[Rem] Synaptic AI Pro

This commit is contained in:
2026-06-07 01:20:10 +07:00
parent 492c1ea9f9
commit 0197fb4aa2
13679 changed files with 0 additions and 1125929 deletions
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b456ef0c7c8c9450bb4f6f07b4e90f7a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-995
View File
@@ -1,995 +0,0 @@
# Changelog
All notable changes to Synaptic AI Pro for Unity will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.23] - 2026-05-22
### Fixed (ESC-0107: `run_csharp` returned `result: null` for almost every snippet)
- **Return-value capture**: `Mono.CSharp.Evaluator.Run` discards return values, so `return X;` snippets always reported null. New approach: detect the last top-level `return` keyword (depth-aware: skips braces, parens, brackets, strings, comments) and rewrite `[prefix...] return X;``[prefix...] SynapticPro.NexusCSharpEval.__SetResult(X);`. Run the rewritten statements, then read the static sink back into the response. Captures values from `foreach (...) { ... } return Y;` (with braces), `if (...) { ... } return X;`, multi-statement bodies with multiplication (`var x = 5; return x * 2;`), and the simple `return "hello";` case.
- **Bare-expression mode preserved**: snippets without `;` still go through `Evaluator.Evaluate` directly.
- **`Debug.Log` output capture**: `Application.logMessageReceived` is now subscribed while a `Run` call is in progress so `Debug.Log` / `LogWarning` / `LogError` lines are mirrored into the `output` field. Previously only `Console.Out` was captured, which Unity's logging doesn't route through.
### Fixed (ESC-0108: HTTP server WebSocket dies after ~30s)
- `http-server.js` heartbeat replaced `ws.ping()/pong` with last-message-seen timestamps. Mono `ClientWebSocket` doesn't auto-pong protocol-level pings (unlike .NET 5+), so the Node side terminated the link every interval. New `UNITY_STALE_TIMEOUT_MS` (default 60s) only closes when no inbound frame arrives — Unity already emits heartbeat / operation-response traffic, so live connections stay open.
### Fixed (HTTP server died on macOS/Linux during domain reload)
- Replaced `Process.Start` with piped stdout/stderr with a `nohup node ... >log 2>&1 </dev/null &` detach. The previous pipe wiring caused node to hit SIGPIPE on the next write after Unity's C# domain reloaded, killing the HTTP server every recompile.
### Fixed (Auto-reconnect didn't engage on fresh installs)
- `enableMCP` default flipped from `false` to `true`. Unity is always a CLIENT of the MCP server (port 8090), so the opt-in master switch was a UX trap — Auto Reconnect checkbox couldn't take effect until users found `Tools > Synaptic Pro > MCP Server: Start`.
- Manual `AI Reconnect` and the new `Auto Reconnect` toggle in Setup → AI Connection also force `enableMCP = true` so the next domain reload doesn't revert.
- Successful `ConnectToMCPServer` persists `enableMCP=true`.
### Fixed (Port-mapping JSON corruption infinite loop)
- `NexusProjectPortManager.LoadMapping` recovery now deletes `.backup` before `File.Move` (Windows otherwise threw on existing target, the silent catch left the corrupt file in place, and the next frame parsed it again — Console flooded at frame rate). Also writes a fresh empty mapping immediately so subsequent readers see valid JSON.
### Added
- **Setup Window → AI Connection tab → Connection Controls Bar**: live MCP connection status, `AI Reconnect` button (silent), `Auto Reconnect` checkbox, `Discord` shortcut. Surfaces the Tools-menu items where users already troubleshoot. `MCP Server: Start/Stop` stays in the menu (advanced).
### Limitations
- `Mono.CSharp.Evaluator` (Unity 2022+ Mono build) does not parse generic TYPE instantiation: `new List<int>()`, `new Dictionary<K, V>()`, `new HashSet<T>()` silently return `result: null`. Workarounds: use arrays (`new int[] {1, 2, 3}`), `System.Collections.ArrayList`, or generic METHOD calls which DO work (`FindObjectsByType<GameObject>(...)`, `GetComponent<T>()`).
---
## [1.2.22] - 2026-05-21 — Emergency Hotfix
### Critical Fix
- **MCP timeout regression (ESC-0102)**: `SynLog.Info` called `EditorPrefs.GetBool` on every log invocation. `EditorPrefs` is main-thread only — calling it from `ListenForMessages` (Task.Run background thread, e.g. WebSocket `ReceiveAsync` handlers) threw silently and killed the listener Task. Every MCP command from Claude Desktop / Cursor then timed out.
- **Fix**: `SynLog` now caches the verbose flag in a `volatile bool` at `[InitializeOnLoadMethod]` time. Info/Warn read the cache (thread-safe). `Set` updates both the cache and `EditorPrefs`.
- Introduced in v1.2.20 with the SynLog wrapper; surfaced under load with the v1.2.21 detached-spawn pipeline. Affected all platforms (Win + Mac).
- **`NexusEditorMCPService.lastConnectionCheckTime` epoch mismatch**: Written via `ThreadSafeTime()` (Stopwatch-since-classload), compared against `Time.realtimeSinceStartup` (Editor-since-startup). After the first domain reload Stopwatch reset to 0 while `Time.realtimeSinceStartup` kept counting — the gate `currentTime - lastConnectionCheckTime > 2f` became permanently true, forcing the reconnect phase to fire every frame and tearing down established sessions.
- **Fix**: `Update()` calibrates `ThreadSafeTime()` against `Time.realtimeSinceStartup` on the first main-thread tick. Both sides now share the same epoch.
### Added
- **`unity_run_csharp` meta-tool (SuperSave) / `run_csharp` Editor operation**: Equivalent of Blender's `run_python`. Execute arbitrary C# against the running Editor (UnityEngine / UnityEditor / Linq / Newtonsoft.Json pre-imported). Uses `Mono.CSharp.Evaluator` (instance API, all-AppDomain assembly injection) — does NOT trigger an AssemblyReload, so the connection stays alive.
### Diagnostics
- `index-supersave.js`: added `wss.on('error')` + per-socket `ws.on('error')`, connection-info logging, `unityWebSocket assigned` confirmation, `readyState !== OPEN` precheck, and `send()` callback so write failures surface immediately instead of bleeding into the 60s timeout.
- `NexusWebSocketClient.ReceiveLoop` (HTTP-bridge path): added missing `EndOfMessage` concatenation. Messages over 4096B were truncated mid-chunk and failed JSON parse.
---
## [1.2.21] - 2026-05-20
### Fixed
- **Windows HTTP Server Cascade Kill (ESC-0095)**: Root cause finally identified. Unity Editor on Windows assigns itself a Win32 Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. Any child process started via `Process.Start` inherits the Job and gets killed when Unity manipulates it on assembly reload / PlayMode transitions. This is the long-standing reason HTTP server "dies" after several script edits — and why the v1.2.10 → v1.2.11 internal-to-external rewrite did not fix it (the external Node.js was still inside Unity's Job).
- **Fix**: Replaced `Process.Start` on Windows with `CreateProcessW` P/Invoke using `CREATE_BREAKAWAY_FROM_JOB | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP`. The spawned `node.exe` now runs fully independent of Unity's Job Object.
- **PID Recovery**: Node PID stored in `SessionState` + `EditorPrefs`. After domain reload, `[InitializeOnLoadMethod]` re-attaches by PID and only reconnects the WebSocket — the HTTP process itself survives.
- **Parent Watchdog (orphan guard)**: `http-server.js` now accepts `--parent-pid={UnityPID}` and self-terminates if Unity dies. Prevents zombie `node.exe` even when `BREAKAWAY` succeeds.
- **Detached log file**: `--log={path}` routes Node output to a file because stdout pipes break under `DETACHED_PROCESS`. Logs land in `MCPServer/logs/http-server.log`.
- **Fallback**: If `CREATE_BREAKAWAY_FROM_JOB` is denied (ACCESS_DENIED — Unity's Job missing `JOB_OBJECT_LIMIT_BREAKAWAY_OK`), retries with `DETACHED_PROCESS` only. Behaviour matches v1.2.20 in that fallback case, but the parent-PID watchdog still guards orphans.
- **macOS / Linux behaviour unchanged**: Those platforms have no Job-Object-equivalent cascade-kill mechanism, so the legacy `Process.Start` path is retained.
### Reference
- Burst Compiler (Unity's own package) uses the same `CREATE_BREAKAWAY_FROM_JOB` technique for the same reason. See `BclApp.cs` in `com.unity.burst`.
---
## [1.2.20] - 2026-05-10
### Fixed
- **Async Thread Crash on Disconnect (ESC-0025)**: `OnConnectionLost()` accessed `Time.realtimeSinceStartup` from non-main threads, throwing `get_realtimeSinceStartup_Injected can only be called from the main thread` and killing `ListenForMessages`. After this exception, all subsequent tool executions failed silently until Unity restart.
- Added `ThreadSafeTime()` helper using `System.Diagnostics.Stopwatch` for thread-agnostic timing
- `OnConnectionLost()` now uses `ThreadSafeTime()` when called from async/WebSocket contexts
- Reported repeatedly on Windows v1.2.19; should now self-recover instead of requiring restart
- **Main Window Repaint Recursion**: `ThrottledRepaint()` was calling itself instead of `Repaint()`, causing infinite recursion when triggered. Fixed to call `Repaint()` properly.
### Changed
- **Editor Log Volume**: Introduced `SynLog` wrapper allowing internal Info/Warning logs to be toggled via Setup Window → HTTP Server tab → "Verbose Logs". Errors are always logged.
- **Setup Window Min Size**: Reduced from 800×800 to 480×480 so the window fits on smaller laptop screens and can be docked alongside other panels.
### Safeguards
- **Auto-Update Path Validation**: Backup/replace of `Synaptic AI Pro/` is now gated by file-size (≥100KB) and marker-file checks before deleting the existing installation. Failed downloads or partial archives no longer wipe the working folder.
---
## [1.2.19] - 2026-04-23
### Fixed
- **Windows HTTP WebSocket Stability**: Fixed HTTP server tab becoming unresponsive on Windows (contributed by OverlordMethuselah777)
- Added reentrancy guard to prevent concurrent Connect() calls
- Connect timeout (5 seconds) prevents indefinite hang
- Background tasks properly tracked and awaited on disconnect
- **MCP Reconnect Storm Prevention**: Fixed reconnect phase machine firing unlimited background tasks
- Added MIN_RECONNECT_INTERVAL (10s) hard gate between reconnect attempts
- All fire-and-forget tasks now tracked with fault logging
- Connect timeout (5s) added to MCP WebSocket handshake
- **Setup Window UI Freeze**: Port check moved to background thread (contributed by OverlordMethuselah777)
- Port check throttled to every 2 seconds with cached result
- **Main Window Repaint Flood**: Added ThrottledRepaint() at 10Hz cap (contributed by OverlordMethuselah777)
### Added
- **MCP Server Start/Stop Menu**: Tools > Synaptic Pro > MCP Server: Start/Stop (contributed by OverlordMethuselah777)
- enableMCP master switch, persisted via EditorPrefs
### Changed
- **Assembly Definition**: Fixed VFX Graph versionDefines expression
---
## [1.2.18] - 2026-04-15
### Added
- **Auto-Update System**: One-click update check on startup (once per day)
- BOOTH/site version: auto-download and replace
- Asset Store version: browser redirect
- **WebSocket Heartbeat**: ping/pong keepalive for connection stability
---
## [1.2.17] - 2026-04-14
### Fixed
- **HTTP Server Tab Performance**: Reduced UI overhead
---
## [1.2.16] - 2026-04-13
### Fixed
- **Unity 6 GUILayout**: Layout compatibility fix
- **Auto-Start**: Domain reload behavior corrected
---
## [1.2.15] - 2026-04-12
### Fixed
- **Setup Window Freeze**: Fixed freeze on large projects
---
## [1.2.14] - 2026-04-11
### Fixed
- **"Hold on" Dialog**: Fixed blocking dialog issue
- **Windows Command Path**: Enhanced Node.js detection
- **Node.js Process Cleanup**: Improved cleanup on exit
- **MCP Port Conflict**: Fixed with multiple Claude Code sessions
---
## [1.2.13] - 2026-04-10
### Fixed
- **Setup Window Repaint Loop**: Fixed continuous repaint
---
## [1.2.12] - 2026-04-09
### Fixed
- **Windows Path with Spaces**: HTTP server startup fix
---
## [1.2.11] - 2026-04-08
### Changed
- **HTTP Server Externalized**: HTTP Server now runs as external Node.js process (`http-server.js`)
- Resolves domain reload and Play Mode stability issues
- HTTP and WebSocket on same port (default: 8086)
- Unity connects via WebSocket automatically
- Project-specific port settings (each Unity project can use different port)
### Added
- **http-server.js**: New standalone HTTP server
- All endpoints: `/`, `/health`, `/tools`, `/categories`, `/tools/search`, `/execute`, `/batch`, `/resources`
- Same port for HTTP and WebSocket (no port conflict with MCP)
- Auto-reconnect on connection loss
### Removed
- **Internal HTTP Server (C#)**: Removed `NexusHTTPServer.cs` - replaced by external Node.js server
---
## [1.2.10] - 2026-04-06
### Fixed
- **MCP Dual-Process Startup Bug**: Fixed critical issue where Claude Code/VSCode starts two MCP processes simultaneously
- Root cause: When extension starts MCP, PID-A listens successfully, PID-B gets EADDRINUSE
- Silent `uncaughtException` handler swallowed error, PID-A dies (stdin closed), PID-B survives but never listened
- Result: MCP stdio works but WebSocket connection fails
- Solution: Added `startServerWithRetry()` with 5 retry attempts and `requestShutdownFromPriorProcess()` WebSocket handover
- Added shutdown message handler for graceful process takeover
- Fixed in both `index.js` and `index-supersave.js`
### Added
- **MCP Resources**: New `resources/list` and `resources/read` protocol support
- `synaptic://tools/reference` - Compact tools reference (~31KB)
- `synaptic://tools/reference/full` - Full markdown reference with inputSchema (~103KB)
- Enables prompt caching for reduced token usage
- **HTTP Resources Endpoints**: New endpoints for tools reference
- `GET /resources` - List available resources
- `GET /resources/read?uri=...` - Read resource by URI
- Root `/` endpoint now includes full tools reference with inputSchema
---
## [1.2.9] - 2026-04-03
### Added
- **Export Package Tool**: New `unity_export_package` tool to create .unitypackage files
- Export multiple asset paths at once (comma-separated)
- Optional dependency inclusion
- Custom output path or auto-generated with timestamp
---
## [1.2.8] - 2026-04-03
### Added
- **Tool Search Endpoint**: New `GET /tools/search` endpoint for keyword-based tool discovery
- Search by keyword in tool name, title, and description
- Optional category filter: `?q=material&category=Material`
- Configurable result limit: `?q=camera&limit=10`
- Relevance scoring for better results ranking
- Example: `curl "http://localhost:8086/tools/search?q=material&limit=5"`
- **MCP Search Tool**: New `search_tools` meta-tool for MCP server
- Search tools without knowing exact category
- Parameters: `query`, `category` (optional), `limit` (optional)
- Returns ranked results with score
- **Console Log Filtering**: New filtering options for `unity_analyze_console_logs` to reduce token usage
- `excludeSynaptic` (default: true): Auto-exclude internal Synaptic logs ([Synaptic], [NexusConsole], etc.)
- `filter`: Include only logs containing specified text
- `exclude`: Exclude logs matching patterns (comma-separated)
- `groupByMessage`: Group duplicate messages with count instead of repeating
- Reduces token usage significantly (1,500-2,000 → 50-100 tokens for typical queries)
### Improved
- **HTTP Server Port Recovery**: Now forcefully kills any process blocking the port on startup
- No more "port already in use" errors after domain reload
- Works on Windows (netstat/taskkill) and macOS/Linux (lsof/kill)
- Reduced retry count (5→3) and delay (2s→500ms) for faster startup
### Fixed
- **MeshRenderer Material Assignment**: Fixed JSON serialization error when setting material via `unity_update_component`
- Root cause: `Color.linear` property caused circular reference during serialization
- Added `ConvertValueForSerialization` handlers for Material, Texture, and UnityEngine.Object types
- Now returns safe serializable object with `name`, `shader`, `assetPath`, `instanceId`
---
## [1.2.7] - 2026-03-27
### Fixed
- **Windows Domain Reload Recovery**: Fixed critical issue where HTTP server could not restart after script recompilation on Windows
- Root cause: Port remained occupied during domain reload due to HttpListener not being properly released
- Added `ForceReleasePort()` method called before assembly reload
- Direct Abort/Stop/Close on HttpListener object for immediate port release
- Added `GC.Collect()` and `GC.WaitForPendingFinalizers()` to ensure port is released
- Replaced `Thread.Abort()` (deprecated in .NET Core) with `Thread.Join(500)` for graceful thread termination
- 100ms sleep after cleanup to ensure OS releases the port
- Resolves "Failed to start HTTP server" error that previously required Unity restart
- **HTTP Server Retry Settings**: Increased retry attempts for better Windows compatibility
- Auto-start retries: 5 → 15 attempts
- Auto-start retry delay: 0.5s → 1.0s
- Manual start retries: 3 → 5 attempts
- Manual start retry delay: 500ms → 1000ms
- Provides more time for Windows to release ports after domain reload
---
## [1.2.6] - 2026-03-23
### Fixed
- **HTTP Server Port Stability**: Improved port release and binding reliability
- Added `Abort()` call on Stop for immediate port release
- Added retry logic on Start to handle TIME_WAIT state (common on Windows)
- Up to 5 retries with 500ms delay for robust port binding
- Prevents "port already in use" errors after rapid restart cycles
---
## [1.2.5] - 2026-02-27
### Added
- **HTTP Prompt Endpoint**: New `GET /prompt` endpoint to fetch AI control instructions directly
- No more manual copy-paste from Setup Window
- Returns the full AI control prompt with endpoint documentation
- Useful for automation and custom integrations
- **Test Runner Auto-Execution**: `unity_run_tests` now supports automated test execution
- `operation="run"` - Start test execution (EditMode or PlayMode)
- `operation="results"` - Get test progress and results
- `operation="list"` - List available tests
- Uses Unity TestRunnerApi with ICallbacks for reliable async execution
- Returns detailed results: passed/failed/skipped counts, duration, error messages
### Fixed
- **Windows Claude Desktop Auto-Setup**: Fixed one-click setup not detecting Claude Desktop config on Windows
- Added support for Microsoft Store version (`%LOCALAPPDATA%\Packages\Claude_*`)
- Added multiple path candidates (`%APPDATA%\Claude`, `%LOCALAPPDATA%\Claude`)
- Improved detection logic to find existing config files first
- Added detailed debug logging for troubleshooting
- **VFX Graph Tools**: Fixed critical reflection issues in VFX Graph manipulation tools
- `unity_vfx_add_context`: Fixed "Index out of range" error when adding contexts
- `unity_vfx_add_parameter`: Fixed "Index out of range" error when adding parameters
- `unity_vfx_add_block`: Fixed "AddChild failed" error when adding blocks
- `unity_vfx_set_attribute`: Fixed "Ambiguous match found" error when setting attributes
- Improved compatibility with Unity 2022.3 LTS and newer VFX Graph versions
- Added child count verification to detect successful adds despite internal exceptions
- **HTTP Server Play Mode Stability**: Fixed server becoming unresponsive after Play mode transitions
- Added `playModeStateChanged` handler to restart server during Play mode changes
- Server now cleanly stops before entering/exiting Play mode and restarts after
- Prevents port occupation issues that caused "server not responding" errors
- **Animator Controller Editor Refresh**: Fixed Animator window not updating after script-based changes
- Added `AssetDatabase.ImportAsset(..., ForceUpdate)` after controller modifications
- Animator window now immediately reflects changes from AI tools
- **MCP Connection Messages**: Improved connection error messages
- Reduced console log noise (only logs 1st and every 5th attempt)
- Clearer message when MCP server is not running
- Suggests starting Claude Desktop/Cursor and using AI Reconnect
## [1.2.4] - 2026-02-03
### Added
- **Dynamic Meta-Tools**: New universal tools for inspecting and modifying any Unity component
- `unity_dynamic_inspect`: Inspect GameObjects, components, scene, hierarchy, prefabs, project
- Discovers all serialized properties with types and current values
- Supports depth-limited hierarchy traversal
- Wildcard search for prefabs (e.g., `Assets/**/*.prefab`)
- `unity_dynamic_modify`: Modify any component property using property paths
- Supports nested properties (e.g., `m_Lens.FieldOfView`)
- Auto-creates components with `createIfMissing` option
- Handles Vector2/3/4, Color, Enum, and all basic types
- `unity_dynamic_create`: Universal creation tool
- GameObjects (empty or primitives)
- Prefab instantiation from any asset path
- Scene loading (single or additive)
- Component addition to existing objects
- **SuperSave Mode Meta-Tools**: Same functionality available as `inspect`, `modify`, `create` in token-saving mode
### Fixed
- **HTTP Server Port Cleanup**: Fixed port remaining occupied after script recompilation
- Added `AssemblyReloadEvents.beforeAssemblyReload` handler to stop server before domain reload
- Added `EditorApplication.quitting` handler to stop server on editor quit
- Prevents "port already in use" errors after script changes
- **Script Creation Path**: `unity_create_script` now supports custom path parameter
- Previously always created scripts in `Assets/Synaptic_Generated/`
- Now accepts `path` parameter (e.g., `Assets/Scripts/Player/`)
- AI can organize scripts in proper project folders
- **Node.js Path Detection for Windows**: Fixed MCP server not starting on Windows when Node.js is installed on D: drive or non-standard locations
- Added `FindNodePath()` function to detect Node.js across multiple installation paths
- Supports nvm-windows, Volta, fnm, and manual installations
- Searches D:, E: drives for users who install on secondary drives
- Falls back to PATH if not found in common locations
- Config now writes full Node.js path instead of just "node"
- **Prefab/Asset Contamination in Scene Analysis**: Fixed critical bug where prefabs loaded in memory were incorrectly included in scene analysis
- `unity_analyze_draw_calls` was reporting objects from prefabs as if they existed in the scene
- `unity_search_objects` and `unity_cleanup_empty_objects` had the same issue
- Added `EditorUtility.IsPersistent()` check to exclude non-scene objects
- Now only returns objects that actually exist in loaded scenes
---
## [1.2.3] - 2026-01-24
### Fixed
- **HTTP API Tool Mapping**: Fixed unmapped tools returning "Unknown operation" error
- Tools without explicit mapping now correctly strip `unity_` prefix
- `/execute`, `/batch`, `/tool/:name` endpoints all work with any tool
- Example: `unity_get_scene_summary``GET_SCENE_SUMMARY`
---
## [1.2.2] - 2026-01-22
### Fixed
- **Token SuperSave Mode**: Fixed execute tool not sending commands to Unity
- Command name now uses lowercase to match Unity's `ConvertCommandToOperationType`
- Fixed PORT environment variable (was MCP_PORT)
- **MCP Config Generation**: All config generators now use selected server mode
- Fixed `GenerateGeminiConfig()` - was hardcoded to index.js
- Fixed `GenerateWindsurfConfig()` - was hardcoded to index.js
- Fixed `GenerateClaudeCodeSpecificConfig()` - was hardcoded to index.js
- Now correctly uses `GetServerScriptPath()` for all clients
- **GetGameObjectsList**: Added parameter aliases for more intuitive usage
- `tag``tagFilter`
- `layer``layerFilter`
- `name``nameFilter`
- **GetComponentDetails**: Now returns useful info for all component types
- Previously returned `null` for unhandled component types
- Now returns basic serializable properties (up to 10)
- Falls back to `{ type_name: "ComponentName" }` if no properties available
---
## [1.2.1] - 2026-01-20
### Fixed
- **Token SuperSave Mode**: Added missing shutdown handlers to `index-supersave.js`
- SIGINT/SIGTERM signal handling
- stdin close detection for proper MCP client cleanup
- Graceful WebSocket and server shutdown
---
## [1.2.0] - 2026-01-19
### Added - Token SuperSave Mode ★
- **New MCP Server Mode**: 99% context reduction with only 3 meta-tools
- `list_categories()` - Discover available tool categories
- `list_tools(category)` - See tools & parameters in a category
- `execute(tool, params)` - Run any of 350+ tools by name
- Works with ALL MCP clients (no dynamic tool loading required)
- Best for long AI sessions - more context for conversation
- Now set as **Recommended** and default in Setup Window
- **Changelog Dialog**: Shows what's new on startup/import
- "Don't show on startup" toggle
- Manual access: Tools → Synaptic Pro → What's New
- Version-aware: Only shows once per version
### Changed
- **Setup Window Redesigned**:
- Token SuperSave Mode at top with ★ Recommended label
- Green highlight for recommended option
- Updated tool counts (350+ tools)
- Clearer mode descriptions
### Improved
- **Error Messages**: Better debugging experience
- "Did you mean...?" suggestions for unknown tools
- Detailed troubleshooting for connection errors
- Timeout error explanations
- Tool info included in error context
- **Tool Registry**: Dynamic loading from tool-registry.json
- No more hardcoded tool definitions in supersave mode
- Easier maintenance and updates
---
## [1.1.9] - 2026-01-15
### Fixed
- **Batch Tool (`unity_execute_batch`)**: Fixed batch execution not working
- Added missing `EXECUTE_BATCH` operation mapping in MCP service
- HTTP Server now auto-converts `operations` format to Unity's `tasks` format
- Both formats now work: `operations` (user-friendly) and `tasks` (native)
- **MCP stdio Protocol**: Changed `console.log` to `console.error` in index.js
- Prevents log messages from corrupting JSON-RPC communication
- Fixes "Unexpected token" JSON parse errors for some users
---
## [1.1.8] - 2025-01-15
### Added
- **Sphere Skybox Mode**: New `sphere`/`dome`/`landscape` type for `unity_create_skybox_from_image`
- Creates inverted sphere mesh with texture on inside
- Perfect for regular landscape photos (non-360°)
- Parameters: `radius`, `followCamera`, `objectName`
- `SkySphereCameraFollow` component keeps sphere centered on camera
- Best for wide panoramic shots and scenic backgrounds
### Fixed
- **Shader PackageRequirements**: Added `PackageRequirements` tags to URP SubShaders
- SynapticWater.shader: URP SubShader now skipped when URP package not installed
- SynapticCaustics.shader: Same fix applied
- Resolves shader compilation errors for users without URP
- Built-in fallback SubShader works correctly on all pipelines
### Changed
- **MCP Server Name**: Changed from `unity` to `unity-synaptic` across all configurations
- Prevents conflicts with other Unity MCP integrations
- Updated: Claude Desktop, Claude Code, Windsurf, Gemini CLI, Codex CLI, Cline
- TOML configs use quoted key: `[mcp_servers."unity-synaptic"]`
- Permissions updated: `mcp__unity-synaptic`
- Mentions updated: `@unity-synaptic`
### Improved
- **HTTP Server Auto-Start**: Server automatically restarts after Unity recompilation
- New "Auto-Start on Load" toggle in HTTP Server tab
- Settings persist via EditorPrefs
- Similar to MCP Auto-Reconnect feature
- **HTTP Server Tool Registry**: Now loads all tools from tool-registry.json
- Previously only 13 hardcoded tools available
- Now exposes all 351 tools via HTTP API
- `/tools` endpoint shows full tool catalog
---
## [1.1.7] - 2025-01-13
### Added
- **HTTP Server Tab**: Direct HTTP API access to Unity tools
- New "HTTP Server" tab in Synaptic Setup window
- Start/Stop HTTP server with one click
- Default port: 8086 (configurable)
- Endpoints: `/health`, `/tools`, `/scene`, `/tool/{name}`
- CORS support, API docs in UI, cURL examples
- **NexusHTTPServer.cs**: Runtime HTTP server component
- HttpListener-based, thread-safe request handling
- **unity_instantiate_prefab Tool**: Place prefabs/FBX assets from project into scene
- Supports any asset path (e.g., `Assets/Models/Chair.fbx`, `Assets/Prefabs/Player.prefab`)
- Optional parameters: name, position, rotation, scale, parent
- Works with .prefab, .fbx, and other importable 3D formats
- Resolves user request for placing existing project assets via CLI
- **execute_menu_item Tool**: Execute Unity menu items via MCP
- Trigger any Unity Editor menu command (e.g., "File/Save", "Edit/Play")
- Enables automation of Editor workflows
- **InstanceID Support**: GameObject lookup by InstanceID
- `FindGameObjectByNameOrId()` helper method
- Accepts both name strings and integer InstanceIDs
- More reliable object identification in complex scenes
- **Start MCP Server Menu**: Tools/Synaptic Pro/Start MCP Server
- Handles paths with spaces correctly
- Port-in-use check prevents duplicate server instances
- Cross-platform support (Windows/macOS)
### Improved
- **MCP Auto-Retry System**: Enhanced for long compilations
- Increased from 10 to 30 retries
- Total wait time: up to 5 minutes (was 2.5 min)
- Applies to: index.js, index-essential.js, hub-server.js
- Prevents timeout errors during large project recompilation
### Changed
- **Menu Items**: Removed emojis from Auto Reconnect menus
---
## [1.1.6] - 2025-12-29
### Fixed - Build Errors
- **NexusEventMonitor.cs**: Fixed Editor-only API usage causing build failures
- `CompilationPipeline` now wrapped in `#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER`
- `OnGUI()` method with `EditorStyles`, `EditorGUILayout` now wrapped in `#if UNITY_EDITOR`
- Resolves: CS0103 errors for `CompilationPipeline`, `EditorApplication`, `EditorUtility`, `EditorStyles`, `EditorGUILayout`
---
## [1.1.5] - 2025-12-13
### 🚀 Major Update - 42,600+ lines added!
### Added - AI Systems
- **GOAP (Goal-Oriented Action Planning) Runtime Engine**
- `GOAPAgent.cs` - Full agent implementation with planning & execution
- `GOAPPlanner.cs` - A* search-based planner for action sequences
- `GOAPActionBase.cs` - Base class for custom actions
- `GOAPDynamicAction.cs` - Runtime-configurable actions
- `GOAPGoal.cs` - Goal definition with priorities
- `WorldState.cs` - State representation for planning
- MCP Tools: CREATE_GOAP_AGENT, ADD_GOAP_ACTION, ADD_GOAP_GOAL, SET_WORLD_STATE, etc.
- **Behavior Tree Runtime**
- `BTNode.cs` - Base node with Success/Failure/Running states
- `BTComposites.cs` - Selector, Sequence, Parallel nodes
- `BTDecorators.cs` - Inverter, Repeater, Succeeder, UntilFail, Cooldown
- `BTLeaves.cs` - Wait, Log, SetBlackboard, CheckBlackboard, MoveTo, etc.
- `BehaviorTreeRunner.cs` - Runtime executor with blackboard
- MCP Tools: CREATE_BEHAVIOR_TREE, ADD_BT_NODE, SET_BT_BLACKBOARD, etc.
### Added - Shaders (URP/HDRP/Built-in Compatible)
- **SynapticWaterPro.shader** - Advanced ocean with Gerstner waves, foam, refraction, caustics
- **SynapticSkyPro.shader** - Procedural sky with volumetric clouds, day/night cycle
- **SynapticToonPro.shader** - Anime-style cel shading with outline, rim light
- **SynapticGrassPro.shader** - GPU-instanced grass with wind animation
- **GrassInstancer.compute** - Compute shader for grass placement
- **CloudNoise.compute** - Procedural cloud generation
- Runtime controllers: DissolveController, ShieldController, GrassRenderer
### Added - Water System
- **OceanSystem.cs** - Wave simulation and water surface management
- **Buoyancy.cs** - Physics-based floating objects
- **WaterPhysics.cs** - Water interaction and splash effects
- MCP Tools: CREATE_OCEAN_SYSTEM, ADD_BUOYANCY
### Added - VFX Graph Editing (6 New Tools)
- `unity_vfx_set_output` - Change blendMode, texture, softParticle
- `unity_vfx_set_block_value` - Modify block values (color, size, turbulence)
- `unity_vfx_set_spawn_rate` - Adjust spawn rate
- `unity_vfx_list_blocks` - List all contexts/blocks with indices
- `unity_vfx_remove_block` - Remove blocks from contexts
- `unity_vfx_get_block_info` - Get detailed block information
- New VFXBuilder methods: SetOutputSettings, SetBlockValue, SetSpawnRate, ListBlocks, RemoveBlock, GetBlockInfo
### Added - VFX Textures (150+ CC0 Kenney Textures)
- Fire/Flame: flame_01~06, fire_01~02, flare_01
- Smoke: smoke_01~10, whitePuff00~24, blackSmoke00~24
- Explosion: explosion00~08
- Sparks: spark_01~07
- Magic: magic_01~05, star_01~09, twirl_01~03
- Trails: trace_01~07
- Effects: muzzle_01~05, slash_01~04, light_01~03, circle_01~05
- CC0 License - free to use and redistribute
- `SetParticleTexture()` method for easy texture assignment
### Added - MCP Auto-Retry System
- Automatic 3 retry attempts with 3-second intervals
- Handles Unity recompilation gracefully
- Success response includes retry info when applicable
- Clear error messages after all retries exhausted
### Added - Script Safety Features
- `READ_SCRIPT` requirement before editing (like Claude Code)
- `UpdateScriptVariable` now requires prior read
### Fixed - VFX
- **Fire Preset**: Now with natural flickering
- Random lifetime (0.8-2.0s), size (0.3-0.7), angle (0-360°)
- Random velocity spread, angular velocity for rotation
- Enhanced turbulence (intensity 3.0, frequency 5)
- **Color Attribute**: Uses Vector3 (RGB) instead of Vector4
- **VFXSlotFloat3 Angle**: Float → Vector3 conversion added
### Fixed - Serialization
- **Vector3 circular reference**: GET_TERRAIN_INFO, GET_LIGHTING_INFO, GET_UI_INFO, GET_PHYSICS_INFO
- Using `Vector3ToString()` helper throughout
### Fixed - MCP Tools
- `unity_update_component`: Accepts both `gameObject`/`component` and `gameObjectName`/`componentName`
- `unity_set_property`: Fixed `propertyName``property` mapping
- Weather system controllers now properly attached (Rain, Snow, Wind, Lightning, Thunderstorm)
### Fixed - Build Errors
- Editor-only code properly wrapped in `#if UNITY_EDITOR`
- Debug namespace ambiguity resolved
- Shader function redefinition and LerpWhiteTo errors
### Changed
- **Auto Reconnect Menu**: Shows "Enable" when OFF, "Disable" when ON
- **Tool Registry**: Regenerated with 342+ tools
- **VFX Presets**: Now use Kenney textures by default
### Technical Stats
- 93 files changed
- 42,641 insertions, 6,561 deletions
- New Runtime scripts: 15+
- New Shaders: 6
- New MCP Tools: 20+
## [1.1.4] - 2025-11-26
### Added
- **HTTP API Endpoints**: Direct Unity control from AI CLIs without MCP
- `GET /health` - Check Unity connection status
- `GET /tools` - List all 246 available tools with descriptions
- `POST /tool/:toolName` - Execute any Unity tool via HTTP
- Compatible with Claude Code, Codex CLI, Gemini CLI, and custom AI tools
- Full tool-registry.json integration for tool discovery
### Fixed
- **WebSocket Message Handling**: Improved operationId recognition
- NexusEditorMCPService now correctly reads operationId from parameters
- Fixed timeout issues when calling tools via HTTP endpoints
- Unified response format between MCP stdio and HTTP/WebSocket paths
### Changed
- Version bumped to 1.1.4 across all components
- package.json, MCPServer/package.json
- NexusWebSocketClient.cs, NexusEditorMCPService.cs
- NexusSetupWindow.cs, NexusSetupManager.cs
## [1.1.3] - 2025-11-25
### Added
- **LM Studio Essential Mode**: New lightweight 80-tool configuration optimized for LM Studio and Cursor
- 80 carefully selected essential tools (67% reduction from full 246 tools)
- 62% smaller file size (90KB vs 239KB)
- Perfect for local AI workflows without subscription costs
- Includes: GameObject, Camera, Scene, UI, Screenshot, Animation basics
- Removed: Scripting, GOAP, Weather, Advanced VFX, Batch operations
- **3-Mode Setup Window**: Easy selection for different AI clients
- Claude Desktop/Cursor (Full 246 tools)
- Cursor/LM Studio Essential (80 tools) ← NEW!
- GitHub Copilot (Dynamic 8→N tools)
- **index-essential.js**: Optimized server script for essential mode
### Fixed
- **LM Studio Configuration**: Added missing `cwd` parameter to LM Studio mcp.json
- Tools now correctly loaded and recognized by LM Studio
- Proper working directory ensures Node.js modules load correctly
## [1.1.2] - 2025-11-24
### Fixed
- **Cinemachine 3.x Complete Support**: Full API compatibility
- Fixed all 27 compilation errors when using Cinemachine 3.1.5
- API Changes: Enum names (PositionModes, RotationModes, UpdateMethods)
- API Changes: LensSettings.Orthographic → ModeOverride pattern
- API Changes: ICinemachineCamera → CinemachineCamera casting
- API Changes: Component property structs (PositionComposer, RotationComposer, Deoccluder)
- API Changes: InputAxisController.Controllers.Length → Count
- API Changes: ImpulseSource.SignalDefinition → ImpulseDefinition
- API Changes: CinemachineDeoccluder.ObstacleAvoidance → AvoidObstacles
- FreeLook implementation: Added missing CinemachineRotationComposer component
- Added GetNoiseProfile() helper function for CM3
- Readonly struct property handling (get-modify-set pattern)
- **Advanced Material Properties**: JSON parsing
- Fixed nested `properties` parameter parsing
- Properties now correctly applied (color, metallic, smoothness, emission)
- Added detailed debug logging for property parsing
- Backward compatibility maintained for direct parameter format
### Improved
- **FreeLook Camera UX**: Clearer error handling
- Made `follow` parameter required (removed confusing auto-dummy-target)
- Detailed error messages with cause + solutions + example code
- Tool description improvements in index.js and tool-registry.json
- Success messages show all created components
- Cleanup on error (removes partially-created camera objects)
### Technical
- Both Cinemachine 2.9.x and 3.1.x fully supported via preprocessor directives
- No breaking changes - all existing code continues to work
- Enhanced logging throughout Cinemachine operations
## [1.1.1] - 2025-11-23
### Fixed
- **Cinemachine Compatibility**: Both v2 and v3 support
- Fixed assembly reference issues for Cinemachine 2.9.7
- Added proper version detection for Cinemachine v2 (`CINEMACHINE_2`) and v3 (`CINEMACHINE_3`)
- Assembly definition now handles both `Cinemachine` (v2) and `Unity.Cinemachine` + `Unity.Splines` (v3)
- **URP Material Support**: Universal Render Pipeline compatibility
- Added render pipeline detection with caching for performance
- `CreatePrimitiveWithMaterial()` helper automatically applies URP/HDRP/Legacy shaders
- Fixed pink material issue in URP projects when creating primitives
- Updated all GameObject creation functions to use pipeline-compatible materials
- Updated all game template functions (FPS, Platformer, RPG, Puzzle, Racing, Strategy)
- Shader not found warnings added for debugging
### Improved
- **Code Quality**: Refactored primitive creation
- Eliminated code duplication in `CreateGameObject()` function
- Centralized material creation logic
- Performance optimization: Cached render pipeline detection
- Updated 50+ primitive creation locations across codebase
## [1.1.0] - 2025-11-23
### Added
#### Dynamic Tool Loading System (GitHub Copilot Support)
- **MCP Hub Server** (`hub-server.js`): Dynamic tool loading for GitHub Copilot
- Starts with 8 essential management tools
- Dynamic tool loading via `select_tools()` by category or keywords
- Support for `notifications/tools/list_changed` (MCP spec 2025-06-18)
- Automatic tool list refresh in GitHub Copilot (VS Code)
- **No OpenAI API required**: Text-based keyword matching and category search
- 31 tool categories for comprehensive Unity feature coverage
- Access all 246 tools dynamically without hitting IDE tool limits
- Unity WebSocket integration (port 8080) matching index.js protocol
#### Prompt Caching Support (Claude Desktop)
- **Tool Catalog Resource** (`unity://tools/catalog`): Dramatic session capacity improvement
- **Verified Results (11 actual tool calls)**:
- First request: 57,511 tokens (tool definitions cached)
- 2-11 requests average: 1,596 tokens/call
- **Token reduction: 97.2%** per subsequent call
- Total used: 73,476 tokens (11 calls)
- Remaining: 116,524 tokens (190,000 budget)
- **Session Capacity**: ~84 total tool calls per session
- Practical development: 70-90 operations
- With heavy data fetching: 50-60 operations
- **Use Cases Enabled**:
- Create 3 UI screens (20 ops each) = 60 calls
- 10 debugging iterations
- 20 adjustments/confirmations
- Total: 90 operations in single session
#### Lightweight Scene Information Tools
- **unity_get_scene_summary**: Fast scene overview (<200KB)
- Scene name, GameObject count, cameras, lights
- Root GameObjects list (max 50)
- Replaces heavy `unity_get_scene_info` for large scenes
- **unity_get_gameobjects_list**: Filtered GameObject queries (<50KB)
- Filter by layer, tag, name (contains), active state
- Returns GameObject names, IDs, paths, metadata
- Max 100 results per query
- **unity_get_gameobject_detail**: Individual GameObject inspection (<10KB)
- Detailed transform, components, children, parent info
- Find by name or instanceId
- Component-specific details (MeshRenderer, Light, Camera, etc.)
- **unity_get_scene_changes_since**: Incremental scene updates
- Timestamp-based differential updates
- Returns added, removed, modified GameObjects
- Efficient monitoring for large scenes
#### Setup Window AI Client Selection
- **AI Tool Selection UI**: Choose between Full Mode and Dynamic Mode
- Claude Desktop / Cursor: Full Mode (246 tools, prompt caching)
- GitHub Copilot: Dynamic Mode (8→dynamic tools, notifications)
- Mode-specific setup instructions and success messages
- Automatic server path selection (index.js or hub-server.js)
#### Infrastructure
- **Tool Loader System** (`utils/tool-loader.js`)
- **31 tool categories**: GameObject, Transform, Material, Lighting, Camera, Physics, UI, Animation, Cinemachine, Scene, GOAP, Audio, Input, VFX, Shader, Weather, TimeOfDay, Editor, Package, Build, Monitoring, AssetManagement, Optimization, Batch, GameSystems, AI, Debug, Timeline, Scripting, Screenshot, Utility
- **Text-based keyword matching** with relevance scoring (no API required)
- Category-based filtering with default presets
- Multi-keyword support with score boosting
- Input normalization (arrays, strings, comma/pipe separated)
- **Tool Registry** (`tool-registry.json`)
- Pre-generated metadata for all 246 tools
- Category assignments and descriptions
- **No external API dependency**: Generated via `generate-simple-registry.js`
- Tool name, title, description, and category information
- **Optional OpenAI Integration** (`utils/embedding.js`)
- Semantic search enhancement (if OPENAI_API_KEY provided)
- Automatic fallback to text matching when unavailable
- Not required for normal operation
### Changed
- **MCP Server Architecture**: Dual-mode support
- `index.js`: Full Mode server (246 tools, all clients)
- `hub-server.js`: Dynamic Mode server (GitHub Copilot only)
- **Setup Window**: Enhanced with AI client selection
- Visual selection buttons with mode explanations
- Dynamic info boxes based on selection
- Mode-specific success messages
- **Tool Count**: Corrected to 246 unique tools
- Verified count (247 registrations, 1 duplicate removed)
- Updated all user-facing documentation
### Technical Details
- **MCP Capabilities**:
- Full Mode: `tools: {}, resources: {}` (Prompt Caching via unity://tools/catalog)
- Dynamic Mode: `tools: { listChanged: true }` (Notifications for dynamic tool updates)
- **Hub Server Communication**:
- Unity WebSocket server on port 8080
- Request/response tracking with pendingRequests Map
- Protocol matching index.js for consistency
- Proper error handling and message routing
- **Tool Search System**:
- Primary: Text-based keyword matching with relevance scoring
- Fallback chain: Multi-keyword matching → category filtering
- No external API calls required for normal operation
- Optional: OpenAI Embedding enhancement (if API key provided)
- **Supported AI Clients**:
- ✅ Claude Desktop: Full Mode with Prompt Caching (~52% token reduction verified)
- ✅ Cursor: Full Mode (246 tools, 80-tool warning can be ignored)
- ✅ GitHub Copilot: Dynamic Mode with notifications support (tested)
- **Session Lifespan Improvements**:
- First call: 57,511 tokens (tool definitions cached)
- Subsequent calls: ~1,596 tokens average
- **Result: ~84 total tool calls per session** (vs ~13 without caching)
- **6.5x capacity increase** verified with real usage
### Dependencies
- **Added**: `openai` ^4.20.0 (optional - for enhanced semantic search only)
- **Not required**: Hub server works with text-based keyword matching by default
- Only needed if you want embedding-based semantic search enhancement
### Performance
- Scene information retrieval: 80-95% size reduction for large scenes
- Claude Desktop token usage: 52% reduction verified
- GitHub Copilot: All 246 tools accessible within 80-tool limit
## [1.0.3] - 2025-11-21
### Added
- **One-Touch MCP Setup**: Automatic configuration for multiple AI tools with a single click
- Configure Claude Desktop, Cursor, and VS Code simultaneously
- `~/.cursor/mcp.json` automatically created for Cursor
- `.vscode/mcp.json` automatically created for VS Code
- **Smart Merge**: Preserves existing MCP server configurations, only adds/updates unity-synaptic
- No manual path configuration needed
- Setup Window button: "Complete MCP Setup"
- **unity_capture_grid**: Auto-split Game View into grid and capture all cells
- Grid sizes: "2x2", "3x3", "4x4", up to "5x5"
- Each cell saved as separate file with position info (basename_r0_c0.png, etc.)
- Perfect for systematic UI analysis and debugging
- Works with Canvas Overlay UI
- **unity_capture_ui_element**: Capture specific UI element by GameObject name
- Automatically finds element and calculates screen bounds
- Works with all Canvas render modes (Overlay, Camera, World Space)
- No need to manually specify coordinates
- Example: `elementName: "MoveButton"` captures just that button
- **unity_get_screenshot_result**: Async result retrieval for Play mode captures
- Call after 3 seconds when capture returns `"status": "pending"`
- Returns screenshot path, width, height
- Works with all async capture operations
### Fixed
- **Screenshot Capture**: Complete overhaul of all screenshot tools to properly capture Canvas/UI
- **CaptureGameView**:
- Automatically enters Play mode if needed to capture Canvas Overlay
- 60-frame wait for rendering stabilization before capture
- Captures at native Game View resolution (fixed 3024x40 bug)
- Exits Play mode automatically after capture
- MCP tool description enhanced to force LLM to wait 3 seconds
- **CaptureRegion**:
- Game View mode now requires Play mode for Canvas Overlay capture
- Captures full screenshot then extracts specified region
- Uses actual screenshot dimensions instead of fixed resolution
- Correctly captures Canvas elements in all render modes
- **CaptureUIElement**:
- Fixed Canvas Overlay coordinate conversion (use GetWorldCorners directly)
- Fixed Y-coordinate inversion bug (both GetWorldCorners and GetPixels use bottom-left origin)
- Added dimension validation before texture creation
- Added debug logging for troubleshooting
- **All Capture Tools**:
- Auto-append .png extension if not specified
- Case-insensitive extension check
- Improved error messages
### Technical Details
- **60-Frame Wait Mechanism**: `ScreenshotFrameUpdate()` method ensures UI is fully rendered
- **EditorPrefs State Persistence**: Survives domain reload when entering Play mode
- **Canvas Overlay Support**: Direct coordinate usage without WorldToScreenPoint conversion
- **Y-Coordinate Fix**: No coordinate transformation needed (both systems use bottom-left origin)
- **Async Workflow**: LLM instructions updated to "WAIT EXACTLY 3 SECONDS" before result retrieval
- All screenshot tools now use proper resolution detection
- Canvas Overlay (Screen Space - Overlay) now correctly captured in Play mode
- Canvas Camera and World Space modes work in both Edit and Play mode
## [1.0.2] - 2025-11-20
### Fixed
- **Windows Compatibility**: Resolved MCP server startup issues on Windows
- **Cinemachine 3.x Support**: Added automatic API detection for Cinemachine 3.0+
- Package now supports both Cinemachine 2.9.7 and 3.x versions
- Automatic detection and adaptation to installed version
## [1.0.1] - 2025-11-19
### Added
- **Screenshot Capture Tools**: Three new MCP tools for visual analysis
- `CaptureGameView`: Capture the Game View window
- `CaptureSceneView`: Capture the Scene View window
- `CaptureRegion`: Capture specific regions with coordinates
- Enables Claude Vision to analyze Unity UI layouts
### Known Issues
- Screenshot capture may not correctly capture Canvas/UI elements (fixed in v1.0.3)
## [1.0.0] - 2025-11-15
### Added
- Initial release of Synaptic AI Pro for Unity
- 235+ professional MCP tools for Unity Editor control
- Natural language interface through Claude AI
- Scene management and GameObject manipulation
- Advanced lighting and rendering controls
- Physics and animation systems
- GOAP AI system with natural language planning
- Comprehensive documentation and examples
- Unity 2022.3+ and Unity 6.0+ support
-14
View File
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 716a2d6198cfb4a0aaff26b73e4827a3
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/CHANGELOG.md
uploadId: 920982
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7cddc4397ab494bc191212cf6117e447
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,290 +0,0 @@
================================================================================
Synaptic AI Pro - シェーダーアップグレード計画
================================================================================
作成日: 2025-12-01
目標: Asset Storeで販売可能なクオリティ / 原神レベルのアニメ表現
================================================================================
■ 調査結果サマリー
================================================================================
【参考資料】
- 原神 GDC 2021: "Erta Gao -Erta Gao | GDC Talk -Erta Gao"
- HoyoToon / StarRailNPRShader (オープンソース)
- Guilty Gear Strive / Arc System Works GDC資料
- Asset Store: Flat Kit, Toony Colors Pro 2
【競合分析】
- Flat Kit: $49 - シンプルなフラットシェーディング、使いやすさ重視
- Toony Colors Pro 2: $50 - 多機能、Shader Generator付き
- UTS2 (Unity-chan Toon Shader): 無料 - 日本アニメ向け基本機能
【原神の主要技術】
1. SDFフェイスシャドウ: 9段階補間ライトマップで滑らかな顔影
2. Kajiya-Kayヘアモデル: 二重スペキュラ + タンジェントシフトマップ
3. デュアルカラーSSS: 光側は黄色、影側は赤のグラデーション
4. Parallax Eye: 深度オフセット + 瞳孔拡張 + 多層反射
================================================================================
■ Phase 1: ToonPro 完全版 (優先度: 最高)
================================================================================
【理由】
- Asset Store需要が最も高い
- 原神人気でアニメシェーダー注目度上昇
- 差別化ポイントを作りやすい
【実装項目】
1. SDFフェイスシャドウシステム
- 9方向ライトマップ生成ツール (Editor拡張)
- SDF補間による滑らかな影遷移
- 鼻影の独立制御
ファイル: SynapticToonPro.shader, SDFGenerator.cs
2. Kajiya-Kayヘアシェーダー
- Primary/Secondary Specular
- タンジェントシフトマップサポート
- 異方性ハイライト
ファイル: SynapticHairPro.shader (新規)
3. Parallax Eyeシェーダー
- 深度オフセットによる立体感
- 瞳孔サイズ制御
- 環境マップ反射
- ハイライトレイヤー
ファイル: SynapticEyePro.shader (新規)
4. アウトラインシステム強化
- Screen Space Edge Detection
- Inverted Hull (現行)
- ハイブリッド対応
- 距離に応じた太さ制御
ファイル: SynapticToonPro.shader
5. デュアルカラーSSS
- ライト側: 暖色グラデーション
- シャドウ側: 寒色グラデーション
- ランプテクスチャ対応
ファイル: SynapticToonPro.shader
6. リムライト拡張
- Standard Rim
- Fresnel Rim
- Depth-based Rim
- Directional Rim
- View-dependent Rim
ファイル: SynapticToonPro.shader
【Editorツール】
- SDF Generator Window
- Ramp Texture Editor
- Material Preset Manager
================================================================================
■ Phase 2: WaterPro + GrassPro 強化
================================================================================
【WaterPro改善】
1. FFT Ocean Waves
- Tessendorf FFT実装
- Phillips/JONSWAP スペクトラム
- Compute Shader計算
ファイル: SynapticWaterPro.shader, FFTOcean.compute
2. GPU Caustics
- ライトからのレイキャスト
- カスタムパターンサポート
ファイル: SynapticCaustics.compute
3. Underwater God Rays
- ボリューメトリックライト
- 減衰制御
ファイル: SynapticWaterPro.shader
4. Quadtree Tessellation
- 距離ベースLOD
- カメラ視錐台カリング
ファイル: SynapticWaterPro.shader
5. Hi-Z SSR
- 階層的Zバッファトレーシング
- パフォーマンス最適化
ファイル: SynapticSSR.compute
【GrassPro改善】
1. Compute Shader Instancing
- GPU駆動インスタンシング
- 大量描画対応 (100万本+)
ファイル: SynapticGrassPro.shader, GrassInstancer.compute
2. 風システム強化
- Perlin Noise + Turbulence
- グローバル風方向
- ローカル風ゾーン
ファイル: SynapticGrassPro.shader
3. タイルベースLOD
- 距離に応じた密度制御
- シームレス遷移
ファイル: GrassRenderer.cs
4. インタラクション
- 踏みつけ変形
- 刈り取り/再生
ファイル: GrassInteraction.cs
5. カリング最適化
- Frustum Culling
- Occlusion Culling
- GPU Indirect Drawing
ファイル: GrassInstancer.compute
================================================================================
■ Phase 3: Effects + Sky 強化
================================================================================
【SkyPro改善】
1. Volumetric Clouds
- Ray Marching実装
- Worley + FBM Noise
- 16フレーム時間再構成
ファイル: SynapticSkyPro.shader, CloudNoise.compute
2. 物理ベース大気散乱
- Rayleigh散乱
- Mie散乱
- 時刻変化
ファイル: SynapticSkyPro.shader
3. 銀縁効果 (Silver Lining)
- 雲の縁の光
- God Rays
ファイル: SynapticSkyPro.shader
【DissolvePro改善】
1. 方向選択UI
- X/Y/Z軸選択
- カスタム方向ベクトル
- 球状ディゾルブ
ファイル: SynapticDissolvePro.shader
2. 多層エッジグロー
- 内側/外側グロー
- カラーグラデーション
- パーティクル連携
ファイル: SynapticDissolvePro.shader
3. バーンテクスチャ統合
- カスタムパターン
- アニメーション対応
ファイル: SynapticDissolvePro.shader
【ShieldPro改善】
1. マルチヒットリップル
- 複数衝撃波管理
- C#コントローラー
ファイル: SynapticShieldPro.shader, ShieldController.cs
2. インターセクション強化
- Overwatch風グロー
- ソフトブレンド
ファイル: SynapticShieldPro.shader
3. パターン拡張
- Voronoi
- カスタムテクスチャ
- アニメーション
ファイル: SynapticShieldPro.shader
================================================================================
■ 新規シェーダー候補
================================================================================
1. SynapticHairPro.shader - アニメヘア特化
2. SynapticEyePro.shader - 瞳シェーダー
3. SynapticClothPro.shader - 布・衣服シェーダー
4. SynapticHologramPro.shader - ホログラムエフェクト
5. SynapticPortalPro.shader - ポータルエフェクト
================================================================================
■ Editorツール
================================================================================
1. SDFGenerator.cs
- 顔メッシュからSDF生成
- 9方向ライトマップ出力
- プレビュー機能
2. RampEditor.cs
- グラデーションランプ作成
- プリセット管理
- リアルタイムプレビュー
3. FlowMapPainter.cs
- フローマップペイント
- 方向可視化
- エクスポート機能
4. NoiseGenerator.cs
- 各種ノイズ生成
- チャンネルパッキング
- タイル化対応
5. MaterialPresetManager.cs
- プリセット保存/読込
- カテゴリ管理
- 一括適用
================================================================================
■ 実装状況 (2025-12-01 完了)
================================================================================
【Phase 1 完了】ToonPro + Character Shaders
✅ SynapticHairPro.shader - Kajiya-Kay二重スペキュラ、SSS、風アニメーション
✅ SynapticEyePro.shader - Parallax虹彩、瞳孔拡張、コースティクス、ハイライト
✅ SynapticToonPro.shader強化:
- デュアルカラーSSS (Genshin Style)
- 5種類のリムライト (Standard, Fresnel, Depth, Directional, View)
- 距離フェード対応アウトライン
【Phase 2 完了】Water + Grass
✅ GrassInstancer.compute - GPU駆動インスタンシング、LOD、カリング
✅ GrassRenderer.cs - Compute Shader連携、100万本以上対応
【Phase 3 完了】Effects + Sky + Controllers
✅ CloudNoise.compute - Worley/Perlin FBM 3Dノイズ生成
✅ DissolveController.cs - 方向制御、パーティクル連携、イベント
✅ ShieldController.cs - マルチヒットリップル、ダメージ状態、再生
【LLM操作対応 完了】NexusExecutor統合 (すべてLLMから直接操作可能)
✅ GENERATE_SDF_TEXTURE - SDFフェイスシャドウテクスチャ生成
✅ GENERATE_RAMP_TEXTURE - シャドウランプテクスチャ生成 (8プリセット)
✅ GENERATE_CLOUD_NOISE - 3Dクラウドノイズテクスチャ生成
✅ ADD_DISSOLVE_CONTROLLER - DissolveController追加
✅ ADD_SHIELD_CONTROLLER - ShieldController追加
✅ ADD_GRASS_RENDERER - GrassRenderer追加
✅ TRIGGER_DISSOLVE - ディゾルブ効果トリガー
✅ TRIGGER_SHIELD_HIT - シールドヒット効果トリガー
✅ CREATE_WATER - 水システム作成 (ocean/pool/river)
※ エディターメニュー削除済み - すべてLLM経由で操作
================================================================================
■ 品質目標
================================================================================
【最低目標】
- Asset Store上位シェーダーと同等機能
- Flat Kit / Toony Colors Pro 2 を超える専門性
【理想目標】
- 原神/崩壊スターレイル相当のビジュアル
- HoyoToon同等の機能をワンパッケージで提供
- LLM連携による差別化
================================================================================
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 3568f9a3426394e2890764556f834493
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Docs/ShaderUpgradePlan.txt
uploadId: 920982
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: bfa9af46466134010a9f42485548914a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,586 +0,0 @@
# Synaptic AI Pro - Tool Reference (350 Tools)
Complete reference for all available MCP tools in Synaptic AI Pro for Unity.
## Table of Contents
- [GameObject (11 tools)](#gameobject)
- [Transform (4 tools)](#transform)
- [Camera (13 tools)](#camera)
- [Cinemachine (9 tools)](#cinemachine)
- [Lighting (10 tools)](#lighting)
- [Material (9 tools)](#material)
- [Shader (13 tools)](#shader)
- [VFX (41 tools)](#vfx)
- [Animation (14 tools)](#animation)
- [Audio (14 tools)](#audio)
- [Physics (6 tools)](#physics)
- [UI (15 tools)](#ui)
- [Scene (10 tools)](#scene)
- [Input (13 tools)](#input)
- [Weather (9 tools)](#weather)
- [TimeOfDay (4 tools)](#timeofdday)
- [GOAP AI (10 tools)](#goap-ai)
- [AI (3 tools)](#ai)
- [GameSystems (5 tools)](#gamesystems)
- [Scripting (9 tools)](#scripting)
- [Editor (7 tools)](#editor)
- [Screenshot (4 tools)](#screenshot)
- [Monitoring (10 tools)](#monitoring)
- [AssetManagement (12 tools)](#assetmanagement)
- [Optimization (7 tools)](#optimization)
- [Batch (5 tools)](#batch)
- [Build (4 tools)](#build)
- [Package (3 tools)](#package)
- [Debug (6 tools)](#debug)
- [Timeline (2 tools)](#timeline)
- [Utility (26 tools)](#utility)
- [Other (42 tools)](#other)
---
## GameObject
| Tool | Description |
|------|-------------|
| `unity_create_gameobject` | Create a new GameObject in Unity scene |
| `unity_update_gameobject` | Update properties of an existing GameObject |
| `unity_delete_gameobject` | Delete a GameObject from the scene |
| `unity_add_component` | Add a component to a GameObject |
| `unity_update_component` | Update component properties |
| `unity_set_active_scene` | Set the active scene in multi-scene editing |
| `unity_get_gameobjects_list` | Get filtered list of GameObjects |
| `unity_get_gameobject_detail` | Get detailed information for a specific GameObject |
| `unity_duplicate_gameobject` | Duplicate an existing GameObject |
| `unity_find_gameobjects_by_component` | Find all GameObjects with specific component |
| `unity_get_gameobject_details` | Get detailed information about a specific GameObject |
---
## Transform
| Tool | Description |
|------|-------------|
| `unity_set_transform` | Set position, rotation, and scale of a GameObject |
| `unity_vfx_remove_block` | Remove a block from a VFX context |
| `unity_remove_package` | Remove an installed Unity package |
| `unity_move_asset` | Move an asset to a different folder |
---
## Camera
| Tool | Description |
|------|-------------|
| `unity_setup_camera` | Setup camera in the scene |
| `unity_create_virtual_camera` | Create a Cinemachine Virtual Camera with follow and look at targets |
| `unity_create_freelook_camera` | Create a Cinemachine FreeLook Camera for third-person orbiting |
| `unity_update_virtual_camera` | Update settings of an existing Cinemachine Virtual Camera |
| `unity_create_state_driven_camera` | Create a State-Driven Camera that switches based on Animator state |
| `unity_create_clear_shot_camera` | Create a Clear Shot Camera for dynamic shot selection |
| `unity_create_blend_list_camera` | Create a Blend List Camera that manages prioritized child cameras |
| `unity_set_camera_priority` | Change the priority of a Cinemachine virtual camera |
| `unity_set_camera_enabled` | Enable or disable a Cinemachine virtual camera |
| `unity_create_mixing_camera` | Create a Mixing Camera that blends multiple child cameras |
| `unity_update_camera_target` | Update Follow and/or LookAt targets of a Cinemachine camera |
| `unity_get_active_camera_info` | Get information about the currently active Cinemachine virtual camera |
| `unity_get_camera_info` | Get information about cameras in the scene |
---
## Cinemachine
| Tool | Description |
|------|-------------|
| `unity_setup_cinemachine_brain` | Setup Cinemachine Brain on a camera |
| `unity_create_dolly_track` | Create a Cinemachine Dolly Track with waypoints |
| `unity_add_confiner_extension` | Add Confiner Extension to restrict camera movement |
| `unity_create_impulse_source` | Create an Impulse Source for camera shake effects |
| `unity_add_impulse_listener` | Add an Impulse Listener extension to receive shake effects |
| `unity_create_target_group` | Create a Target Group for managing multiple camera targets |
| `unity_update_brain_blend_settings` | Update the default blend settings on the Cinemachine Brain |
| `unity_setup_blend_tree` | Create a blend tree for smooth animation transitions |
| `unity_setup_advanced_blend_tree` | Create complex blend trees for animation |
---
## Lighting
| Tool | Description |
|------|-------------|
| `unity_setup_lighting` | Setup lighting in the scene with extended options and presets |
| `unity_setup_lighting_preset` | Apply professional lighting presets to the scene |
| `unity_setup_reflection_probe` | Setup reflection probes for realistic reflections |
| `unity_create_light_probe_group` | Create light probe groups for dynamic GI |
| `unity_setup_volumetric_fog` | Create atmospheric volumetric fog effects |
| `unity_create_lightning_effect` | Create lightning strikes with flash effects |
| `unity_create_skybox_blend` | Blend between day and night skyboxes |
| `unity_create_skybox_from_image` | Create a Skybox from panoramic/HDRI, 6-sided, or sphere mode (for regular landscape photos) |
| `unity_setup_lighting_scenarios` | Create lighting scenarios for different moods |
| `unity_get_lighting_info` | Get information about lights in the scene |
---
## Material
| Tool | Description |
|------|-------------|
| `unity_create_material` | Create a new material |
| `unity_setup_material` | Create or modify materials with PBR properties |
| `unity_create_advanced_material` | Create materials with advanced settings and textures |
| `unity_create_material_property_block` | Modify material properties without creating instances |
| `unity_vfx_set_color_gradient` | Set color gradient for ColorOverLife blocks |
| `unity_set_material_property` | Modify properties of an existing material |
| `unity_get_material_properties` | Get all properties and values from a material |
| `unity_get_material_details` | Get detailed information about a material |
| `unity_get_material_info` | Get information about materials used in scene |
---
## Shader
| Tool | Description |
|------|-------------|
| `unity_create_shader_property_animator` | Animate shader properties like color, float, or vector |
| `unity_animate_shader_texture` | Create scrolling, flipbook, or rotating texture animations |
| `unity_create_shader_gradient` | Apply gradient effects to materials |
| `unity_create_shader_graph` | Create Shader Graph with natural language |
| `unity_create_water_material` | Create Genshin Impact-quality water with physics-based reflection |
| `unity_create_toon_material` | Create stylized toon/cel-shaded material |
| `unity_create_hair_material` | Create realistic/stylized hair material |
| `unity_create_eye_material` | Create realistic/stylized eye material |
| `unity_fix_urp_particle_shaders` | Fix particle materials for URP compatibility |
| `unity_read_shader` | Read the content of a .shader file |
| `unity_modify_shader` | Modify an existing .shader file |
| `unity_analyze_shader` | Analyze a shader's structure |
| `unity_read_shader_graph` | Read and parse a Unity ShaderGraph file |
---
## VFX
| Tool | Description |
|------|-------------|
| `unity_create_visual_effect` | Create complex visual effects combining particles, lights |
| `unity_create_decal` | Create decal projections for details |
| `unity_setup_color_grading` | Apply color grading for cinematic look |
| `unity_create_lens_flare` | Create realistic lens flare effects |
| `unity_create_screen_shake` | Apply screen shake effect for dramatic impact |
| `unity_create_screen_fade` | Create fade in/out transitions |
| `unity_create_vignette_effect` | Add cinematic vignette framing |
| `unity_create_chromatic_aberration` | Add lens distortion and color separation |
| `unity_vfx_set_spawn_rate` | Set the spawn rate of a VFX Graph |
| `unity_vfx_list_blocks` | List all contexts and blocks in a VFX Graph |
| `unity_vfx_get_block_info` | Get detailed information about a VFX block |
| `unity_create_bloom` | Create bloom effect with auto-generated shader |
| `unity_create_film_grain` | Create film grain effect |
| `unity_create_motion_blur` | Create motion blur effect |
| `unity_create_depth_of_field` | Create depth of field effect |
| `unity_create_lens_distortion` | Create lens distortion effect |
| `unity_setup_urp_settings` | Configure Universal Render Pipeline settings |
| `unity_setup_hdrp_settings` | Configure High Definition Render Pipeline settings |
| `unity_setup_post_processing` | Configure post-processing effects stack |
| `unity_create_vfx_graph` | Create VFX particle effects with advanced features |
| `unity_set_vfx_property` | Modify exposed properties of a VFX Graph |
| `unity_get_vfx_properties` | Get all exposed properties of a VFX Graph |
| `unity_trigger_vfx_event` | Send event to VFX Graph (play, stop, custom) |
| `unity_vfx_create` | Create a new VFX Graph asset |
| `unity_vfx_add_context` | Add a context (Spawn, Initialize, Update, Output) |
| `unity_vfx_add_block` | Add a block to a context |
| `unity_vfx_add_operator` | Add an operator node to VFX Graph |
| `unity_vfx_link_contexts` | Link two contexts together |
| `unity_vfx_get_structure` | Get the structure of a VFX Graph |
| `unity_vfx_compile` | Compile and save the VFX Graph |
| `unity_vfx_get_available_types` | List all available context, block, and operator types |
| `unity_vfx_add_parameter` | Add an exposed parameter to VFX Graph |
| `unity_vfx_connect_slots` | Connect output slot to input slot |
| `unity_vfx_set_attribute` | Set attribute value on a SetAttribute block |
| `unity_vfx_create_preset` | Create a complete VFX Graph from preset |
| `unity_vfx_configure_output` | Configure VFX output context settings |
| `unity_read_vfx_graph` | Read and analyze an existing VFX Graph |
| `unity_modify_vfx_graph` | Modify an existing VFX Graph asset |
| `unity_analyze_vfx_graph` | Analyze a VFX Graph asset structure |
| `unity_vfx_set_output` | Set output context settings on a VFX Graph |
| `unity_vfx_set_block_value` | Set a value on a VFX block |
---
## Animation
| Tool | Description |
|------|-------------|
| `unity_create_animation` | Create animation for GameObject |
| `unity_create_animator_controller` | Create a new Animator Controller |
| `unity_add_animation_state` | Add a new state to an Animator Controller |
| `unity_create_animation_clip` | Create a new animation clip with sample curves |
| `unity_add_animation_transition` | Create a transition between animation states |
| `unity_setup_animation_layer` | Add and configure an animation layer |
| `unity_create_animation_event` | Add an event to an animation clip |
| `unity_bake_animation` | Bake runtime animation into an animation clip |
| `unity_import_mixamo_animation` | Import and setup animation from Mixamo |
| `unity_organize_animation_assets` | Organize animation files into proper folder structure |
| `unity_create_animation_layer_mask` | Create an avatar mask for animation layers |
| `unity_retarget_animation` | Retarget animation from one avatar to another |
| `unity_analyze_animation_performance` | Analyze and optimize animation performance |
| `unity_get_animation_details` | Get detailed information about an animation clip |
---
## Audio
| Tool | Description |
|------|-------------|
| `unity_create_audio_mixer` | Create an audio mixer |
| `unity_create_audio_source` | Create and configure an AudioSource component |
| `unity_setup_3d_audio` | Configure 3D spatial audio settings |
| `unity_create_audio_clip` | Import audio file or create procedural audio clip |
| `unity_setup_audio_effects` | Add and configure audio effects |
| `unity_create_reverb_zones` | Create audio reverb zones |
| `unity_setup_audio_occlusion` | Configure audio occlusion |
| `unity_create_adaptive_music` | Create intro+loop music system |
| `unity_setup_audio_triggers` | Configure event-based audio triggers |
| `unity_create_sound_pools` | Create sound variation pools |
| `unity_create_audio_mixing` | Setup real-time audio mixing and ducking |
| `unity_setup_spatial_audio` | Configure advanced spatial audio for VR/AR |
| `unity_create_audio_visualization` | Create visual effects that react to audio |
| `unity_get_audio_details` | Get detailed information about an audio clip |
---
## Physics
| Tool | Description |
|------|-------------|
| `unity_setup_physics` | Setup physics settings for a GameObject or global |
| `unity_setup_navmesh` | Setup navigation mesh |
| `unity_add_collider_extension` | Add Collider Extension for obstacle avoidance |
| `unity_get_physics_settings` | Get Unity physics settings |
| `unity_get_physics_info` | Get information about physics objects in scene |
| `unity_setup_ui_navigation` | Create UI navigation system |
---
## UI
| Tool | Description |
|------|-------------|
| `unity_create_ui` | Create UI elements in Unity |
| `unity_capture_ui_element` | Capture a specific UI element by name |
| `unity_setup_ui_anchors` | Automatically setup anchors and pivots |
| `unity_setup_ui_animation` | Setup UI animations (fade, scale, slide) |
| `unity_create_ui_grid` | Create UI grid layout |
| `unity_create_ui_notification` | Create notification system |
| `unity_create_ui_dialog` | Create modal dialogs |
| `unity_optimize_ui_canvas` | Optimize Canvas for performance |
| `unity_apply_ui_theme` | Apply a complete theme to UI elements |
| `unity_set_ui_colors` | Set colors for UI elements |
| `unity_style_ui_elements` | Apply styling to UI elements |
| `unity_add_ui_effects` | Add visual effects to UI elements |
| `unity_get_ui_info` | Get information about UI elements in scene |
| `unity_setup_ui_canvas` | Configure UI Canvas with different render modes |
| `unity_set_ui_anchor` | Set UI element anchor presets |
---
## Scene
| Tool | Description |
|------|-------------|
| `unity_manage_scene` | Scene management operations |
| `unity_load_scene` | Load a scene in Editor mode |
| `unity_unload_scene` | Unload a scene from the Editor |
| `unity_list_all_scenes` | List all scene files in the project |
| `unity_add_scene_to_build` | Add or remove scenes from Build Settings |
| `unity_get_scene_info` | Get comprehensive scene information |
| `unity_get_scene_summary` | Get lightweight scene overview |
| `unity_get_scene_changes_since` | Get scene changes since a timestamp |
| `unity_capture_scene_view` | Capture a screenshot of the Scene View |
| `unity_pause_scene` | Pause or unpause the scene view |
---
## Input
| Tool | Description |
|------|-------------|
| `unity_setup_custom_input` | Configure custom input actions and bindings |
| `unity_create_gesture_recognition` | Setup gesture recognition for touch or motion |
| `unity_setup_haptic_feedback` | Configure haptic/vibration feedback |
| `unity_create_input_validation` | Create input validation system |
| `unity_setup_accessibility_input` | Configure accessibility features for input |
| `unity_create_input_recording` | Setup input recording and playback |
| `unity_setup_multitouch` | Configure multitouch input handling |
| `unity_create_pinch_zoom` | Setup pinch-to-zoom functionality |
| `unity_setup_swipe_detection` | Configure swipe gesture detection |
| `unity_create_drag_drop` | Setup drag and drop functionality |
| `unity_setup_touch_effects` | Configure visual effects for touch |
| `unity_get_input_settings` | Get Unity input settings |
| `unity_setup_input_system` | Setup Unity Input System with templates |
---
## Weather
| Tool | Description |
|------|-------------|
| `unity_create_terrain` | Create a terrain in Unity |
| `unity_modify_terrain` | Modify terrain height or textures |
| `unity_create_weather_system` | Create complete weather system |
| `unity_set_weather_preset` | Transition to a different weather preset |
| `unity_create_rain_effect` | Create realistic rain effect |
| `unity_create_snow_effect` | Create snow effect with falling snowflakes |
| `unity_create_wind_effect` | Create wind effect affecting objects |
| `unity_create_thunderstorm` | Create complete thunderstorm |
| `unity_get_terrain_info` | Get information about terrain |
---
## TimeOfDay
| Tool | Description |
|------|-------------|
| `unity_create_time_of_day` | Create dynamic day-night cycle |
| `unity_set_time_of_day` | Set specific time in the day-night cycle |
| `unity_create_day_night_preset` | Apply preset lighting conditions |
| `unity_create_time_event` | Create events triggered at specific times |
---
## GOAP AI
| Tool | Description |
|------|-------------|
| `unity_setup_behavior_tree` | Setup a behavior tree AI system |
| `unity_define_goap_goal` | Define a goal for GOAP agents |
| `unity_create_goap_action` | Create an action for GOAP agents |
| `unity_generate_goap_action_set` | Auto-generate action set based on agent type |
| `unity_setup_goap_world_state` | Configure the world state for GOAP planning |
| `unity_debug_goap_decisions` | Visualize and debug GOAP decisions |
| `unity_optimize_goap_performance` | Optimize GOAP agent performance |
| `unity_create_goap_agent` | Create a GOAP AI agent |
| `unity_define_behavior_language` | Define AI behavior using natural language |
| `unity_create_goap_template` | Create GOAP AI from professional templates |
---
## AI
| Tool | Description |
|------|-------------|
| `unity_setup_ml_agent` | Setup a Machine Learning Agent |
| `unity_create_neural_network` | Create a neural network system |
| `unity_create_ai_pathfinding` | Create AI pathfinding with A* algorithm |
---
## GameSystems
| Tool | Description |
|------|-------------|
| `unity_create_game_controller` | Create player controller for different game types |
| `unity_create_state_machine` | Create a state machine for character or game states |
| `unity_setup_inventory_system` | Create an inventory system with UI |
| `unity_create_game_template` | Create complete game templates |
| `unity_quick_prototype` | Create a quick playable prototype |
---
## Scripting
| Tool | Description |
|------|-------------|
| `unity_grep_scripts` | Search for patterns across script files |
| `unity_read_script_range` | Read a specific range of lines from a script |
| `unity_list_script_files` | List script files with glob pattern support |
| `unity_analyze_script` | Comprehensive script analysis for quality issues |
| `unity_modify_script` | Edit existing Unity scripts |
| `unity_edit_script_line` | Edit a specific line in a Unity script |
| `unity_add_script_method` | Add a new method to a Unity script |
| `unity_update_script_variable` | Update variable declaration or value |
| `unity_read_script` | Read the content of a Unity script file |
---
## Editor
| Tool | Description |
|------|-------------|
| `unity_force_refresh_assets` | Force Unity to refresh asset database |
| `unity_invoke_context_menu` | Invoke a [ContextMenu] method on a component |
| `unity_execute_menu_item` | Execute a Unity Editor menu item |
| `unity_get_inspector_info` | Get detailed inspector information |
| `unity_get_selected_object_info` | Get info for currently selected GameObject |
| `unity_get_component_details` | Get details about a specific component |
| `unity_console` | Unity console log operations |
| `unity_analyze_console_logs` | Detailed analysis of Unity console logs |
---
## Screenshot
| Tool | Description |
|------|-------------|
| `unity_capture_game_view` | Capture Game View including Canvas/UI |
| `unity_capture_region` | Capture a specific region |
| `unity_get_screenshot_result` | Retrieve screenshot capture result |
| `unity_capture_grid` | Split and capture Game View as grid |
---
## Monitoring
| Tool | Description |
|------|-------------|
| `unity_get_operation_history` | Get history of Unity operations |
| `unity_create_checkpoint` | Create a checkpoint to restore later |
| `unity_restore_checkpoint` | Restore a previously created checkpoint |
| `unity_monitor_play_state` | Monitor Unity play mode state changes |
| `unity_monitor_file_changes` | Monitor file changes in the project |
| `unity_monitor_compile` | Monitor script compilation events |
| `unity_subscribe_events` | Subscribe to Unity events |
| `unity_get_events` | Get recent Unity events |
| `unity_get_monitoring_status` | Get current monitoring status |
| `unity_monitor_runtime_errors` | Real-time runtime error monitoring |
---
## AssetManagement
| Tool | Description |
|------|-------------|
| `unity_get_asset_dependencies` | Get all dependencies of an asset |
| `unity_check_folder` | Check if folder exists |
| `unity_create_folder` | Create a new folder |
| `unity_list_folders` | List folders in a path |
| `unity_cleanup_empty_objects` | Remove empty GameObjects from scene |
| `unity_rename_asset` | Rename an asset file |
| `unity_delete_asset` | Delete an asset from the project |
| `unity_create_project_snapshot` | Create a snapshot of project state |
| `unity_analyze_dependencies` | Analyze and visualize asset dependencies |
| `unity_export_project_structure` | Export project folder structure |
| `unity_validate_naming_conventions` | Check if assets follow naming conventions |
| `unity_auto_organize_folders` | Automatically organize assets into folders |
---
## Optimization
| Tool | Description |
|------|-------------|
| `unity_optimize_textures_batch` | Batch optimize texture import settings |
| `unity_analyze_draw_calls` | Analyze draw call optimization opportunities |
| `unity_estimate_build_size` | Estimate build size for different platforms |
| `unity_performance_report` | Generate comprehensive performance report |
| `unity_generate_lod` | Automatically generate LOD groups |
| `unity_auto_atlas_textures` | Automatically create texture atlases |
| `unity_check_best_practices` | Analyze scripts for best practices compliance |
---
## Batch
| Tool | Description |
|------|-------------|
| `unity_extract_all_text` | Extract all text content for localization |
| `unity_batch_rename` | Batch rename multiple assets |
| `unity_batch_prefab_update` | Update multiple prefabs with changes |
| `unity_batch_material_apply` | Apply material to multiple objects |
| `unity_batch_prefab_create` | Create prefabs from multiple objects |
---
## Build
| Tool | Description |
|------|-------------|
| `unity_get_build_settings` | Get Unity build settings |
| `unity_get_player_settings` | Get Unity player settings |
| `unity_get_quality_settings` | Get Unity quality settings |
| `unity_get_project_summary` | Get overall project summary |
---
## Package
| Tool | Description |
|------|-------------|
| `unity_list_packages` | List all installed Unity packages |
| `unity_install_package` | Install a Unity package |
| `unity_check_package` | Check if a package is installed |
---
## Debug
| Tool | Description |
|------|-------------|
| `unity_control_game_speed` | Control Unity game speed (time scale) |
| `unity_profile_performance` | Get Unity performance profiling data |
| `unity_debug_draw` | Draw debug shapes in Unity scene |
| `unity_run_tests` | Run Unity Test Runner tests |
| `unity_manage_breakpoints` | Manage debugging breakpoints |
| `unity_debug_bt` | Get debug information about a behavior tree |
---
## Timeline
| Tool | Description |
|------|-------------|
| `unity_setup_avatar` | Configure avatar for a 3D model |
| `unity_create_timeline` | Create a Unity Timeline for cinematics |
---
## Utility
| Tool | Description |
|------|-------------|
| `unity_add_target_to_group` | Add target to Cinemachine Target Group |
| `unity_create_script` | Create a new C# script |
| `unity_create_particle_system` | Create a particle system |
| `unity_create_particle_preset` | Create advanced particle effects |
| `unity_place_objects` | Place multiple objects with pattern |
| `unity_undo_operation` | Undo last Unity operation |
| `unity_redo_operation` | Redo previously undone operation |
| `unity_list_assets` | List assets in the project |
| `unity_search_prefabs_by_component` | Search for prefabs with specific components |
| `unity_find_material_usage` | Find all objects using a material |
| `unity_find_texture_usage` | Find all materials using a texture |
| `unity_find_missing_references` | Find missing references in project |
| `unity_group_gameobjects` | Group multiple GameObjects under parent |
| `unity_batch_import_settings` | Apply import settings to multiple assets |
| `unity_find_unused_assets` | Find assets not referenced in project |
| `unity_create_responsive_ui` | Create responsive UI container |
| `unity_setup_scroll_view` | Create complete scroll view |
| `unity_setup_safe_area` | Setup Safe Area for mobile |
| `unity_get_asset_file_info` | Get file system info about an asset |
| `unity_get_asset_import_settings` | Get import settings for an asset |
| `unity_search_code` | Search for code patterns in project |
| `unity_auto_attach_ui` | Auto attach UI elements to component fields |
| `unity_create_prefab` | Create a prefab from a GameObject |
| `unity_read_particle_system` | Read all Particle System properties |
| `unity_modify_particle_system` | Modify Particle System properties |
---
## Other
42 additional tools for specialized functionality including:
- Behavior Trees (`unity_create_bt_agent`, `unity_add_bt_node`, etc.)
- Water systems (`unity_create_ocean_system`, `unity_add_buoyancy`, etc.)
- HDRP Water (`unity_create_hdrp_water`, `unity_set_hdrp_water_property`)
- Special effects (`unity_create_caustics`, `unity_trigger_dissolve`, etc.)
- Runtime status (`unity_get_runtime_status`, `unity_get_performance_metrics`, etc.)
- Communication (`unity_send_chat_response`, `unity_check_messages`)
---
## Version
- **Tool Count**: 350
- **Version**: 1.2.0
- **Last Updated**: 2026-01-19
---
*Generated from tool-registry.json*
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 4d75915d62f964b04bc074079f55bf91
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Documentation/TOOLS_EN.md
uploadId: 920982
@@ -1,780 +0,0 @@
# Synaptic AI Pro - ツールリファレンス (350ツール)
Synaptic AI Pro for Unity で利用可能な全MCPツールの完全リファレンスです。
---
## 実用的な指示例
AIに自然言語で指示するだけで、複数のツールが自動的に連携して実行されます。
### ゲーム制作
| 指示例 | 使用されるツール |
|--------|------------------|
| 「3Dプラットフォーマーゲームのプロトタイプを作って」 | `create_game_template`, `create_gameobject`, `setup_physics`, `create_virtual_camera` |
| 「FPSゲームの基本セットアップをして」 | `quick_prototype`, `create_game_controller`, `setup_input_system` |
| 「RPGのインベントリシステムを作って」 | `setup_inventory_system`, `create_ui`, `create_script` |
| 「レースゲームのコースを作って」 | `create_terrain`, `place_objects`, `create_dolly_track` |
| 「パズルゲームのグリッドを作って」 | `create_ui_grid`, `create_state_machine`, `place_objects` |
### シーン構築
| 指示例 | 使用されるツール |
|--------|------------------|
| 「森の中の夜のシーンを作って」 | `create_terrain`, `place_objects`, `create_time_of_day`, `setup_lighting_preset` |
| 「雨の降る街のシーンを作って」 | `create_rain_effect`, `setup_volumetric_fog`, `create_audio_source`, `setup_lighting` |
| 「宇宙空間のシーンを作って、星と惑星を配置して」 | `create_skybox_from_image`, `place_objects`, `setup_lighting_preset`, `create_lens_flare` |
| 「水中のシーンを作って」 | `create_ocean_system`, `setup_post_processing`, `create_caustics`, `setup_color_grading` |
| 「砂漠の昼間のシーンを作って」 | `create_terrain`, `setup_lighting_preset`, `create_wind_effect`, `setup_volumetric_fog` |
### カメラワーク
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プレイヤーを追いかける三人称カメラを作って」 | `create_freelook_camera`, `setup_cinemachine_brain` |
| 「カットシーン用のカメラパスを作って」 | `create_dolly_track`, `create_virtual_camera`, `create_timeline` |
| 「複数のキャラクターを映すカメラを作って」 | `create_target_group`, `create_virtual_camera`, `add_target_to_group` |
| 「壁を貫通しないカメラを設定して」 | `create_virtual_camera`, `add_collider_extension`, `add_confiner_extension` |
| 「爆発時にカメラを揺らして」 | `create_impulse_source`, `add_impulse_listener` |
| 「会話シーン用に2人のキャラを映すカメラを作って」 | `create_target_group`, `create_clear_shot_camera` |
### ライティング・天候
| 指示例 | 使用されるツール |
|--------|------------------|
| 「24時間の昼夜サイクルを作って」 | `create_time_of_day`, `create_skybox_blend`, `setup_lighting_scenarios` |
| 「夕焼けのライティングにして」 | `setup_lighting_preset`, `setup_color_grading`, `create_lens_flare` |
| 「雷雨を作って稲妻も光らせて」 | `create_thunderstorm`, `create_lightning_effect`, `create_screen_shake` |
| 「霧の出た早朝のシーンを作って」 | `setup_volumetric_fog`, `setup_lighting_preset`, `create_time_of_day` |
| 「スタジオライティングをセットアップして」 | `setup_lighting_preset`, `setup_reflection_probe`, `create_light_probe_group` |
### VFX・エフェクト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「炎のパーティクルエフェクトを作って」 | `create_vfx_graph`, `vfx_create_preset` |
| 「魔法のエフェクトを作って(青い光とパーティクル)」 | `create_visual_effect`, `create_particle_preset`, `setup_post_processing` |
| 「ブルームとビネットを追加して映画的な見た目にして」 | `create_bloom`, `create_vignette_effect`, `setup_color_grading` |
| 「キャラクターがダメージを受けた時の画面エフェクトを作って」 | `create_screen_shake`, `create_chromatic_aberration`, `create_vignette_effect` |
| 「剣を振った時のトレイルエフェクトを作って」 | `create_vfx_graph`, `vfx_add_context`, `vfx_add_block` |
| 「被写界深度で背景をぼかして」 | `create_depth_of_field`, `setup_post_processing` |
### UI
| 指示例 | 使用されるツール |
|--------|------------------|
| 「HPバーとマナバーのUIを作って」 | `create_ui`, `setup_ui_animation`, `set_ui_colors` |
| 「メインメニュー画面を作って」 | `create_ui`, `create_ui_grid`, `setup_ui_animation`, `apply_ui_theme` |
| 「ダイアログボックスを作って」 | `create_ui_dialog`, `setup_ui_animation` |
| 「モバイル用のジョイスティックUIを作って」 | `create_ui`, `setup_safe_area`, `setup_touch_effects` |
| 「インベントリのグリッドUIを作って」 | `create_ui_grid`, `setup_scroll_view`, `create_drag_drop` |
| 「通知ポップアップシステムを作って」 | `create_ui_notification`, `setup_ui_animation` |
| 「レスポンシブなUIレイアウトを作って」 | `create_responsive_ui`, `setup_ui_anchors`, `setup_safe_area` |
### オーディオ
| 指示例 | 使用されるツール |
|--------|------------------|
| 「BGMシステムを作って、イントロとループを設定して」 | `create_adaptive_music`, `create_audio_mixer` |
| 「3D空間オーディオを設定して」 | `create_audio_source`, `setup_3d_audio`, `setup_audio_occlusion` |
| 「洞窟のリバーブを設定して」 | `create_reverb_zones`, `setup_audio_effects` |
| 「足音のランダムバリエーションを作って」 | `create_sound_pools`, `setup_audio_triggers` |
| 「オーディオビジュアライザーを作って」 | `create_audio_visualization`, `create_vfx_graph` |
### AI・キャラクター
| 指示例 | 使用されるツール |
|--------|------------------|
| 「敵AIを作って、プレイヤーを追いかけて攻撃するようにして」 | `create_goap_agent`, `create_goap_action`, `define_goap_goal` |
| 「NPCのパトロール行動を作って」 | `setup_behavior_tree`, `create_ai_pathfinding`, `setup_navmesh` |
| 「村人AIを作って(巡回、会話、逃走)」 | `create_goap_template`, `setup_goap_world_state` |
| 「ボスAIを作って複数の攻撃パターンを持たせて」 | `create_state_machine`, `create_goap_agent`, `create_animation` |
### アニメーション
| 指示例 | 使用されるツール |
|--------|------------------|
| 「キャラクターのアイドル→歩行→走行のアニメーション遷移を作って」 | `create_animator_controller`, `add_animation_state`, `add_animation_transition` |
| 「ブレンドツリーで移動アニメーションを作って」 | `setup_blend_tree`, `setup_advanced_blend_tree` |
| 「攻撃アニメーションにヒットエフェクトのイベントを追加して」 | `create_animation_event`, `create_visual_effect` |
| 「Mixamoのアニメーションをインポートして設定して」 | `import_mixamo_animation`, `setup_avatar`, `retarget_animation` |
### マテリアル・シェーダー
| 指示例 | 使用されるツール |
|--------|------------------|
| 「原神風の水面シェーダーを作って」 | `create_water_material`, `create_ocean_system` |
| 「トゥーンシェーダーのキャラクターマテリアルを作って」 | `create_toon_material`, `create_shader_graph` |
| 「発光するマテリアルを作って」 | `create_advanced_material`, `set_material_property` |
| 「ディゾルブエフェクトを作って」 | `create_shader_graph`, `trigger_dissolve` |
| 「ホログラムシェーダーを作って」 | `create_shader_graph`, `create_shader_property_animator` |
### スクリプト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プレイヤーの移動スクリプトを作って」 | `create_script`, `read_script`, `modify_script` |
| 「このスクリプトにジャンプ機能を追加して」 | `read_script`, `add_script_method`, `modify_script` |
| 「コード内の"Player"を"Character"に全置換して」 | `grep_scripts`, `modify_script` |
| 「このスクリプトの品質を分析して」 | `analyze_script`, `check_best_practices` |
### プロジェクト管理
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プロジェクトの未使用アセットを見つけて」 | `find_unused_assets`, `analyze_dependencies` |
| 「テクスチャを一括最適化して」 | `optimize_textures_batch`, `auto_atlas_textures` |
| 「アセットを種類別にフォルダ整理して」 | `auto_organize_folders`, `batch_rename` |
| 「ビルドサイズを見積もって」 | `estimate_build_size`, `performance_report` |
| 「欠落参照を見つけて」 | `find_missing_references`, `analyze_dependencies` |
### デバッグ・テスト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「コンソールのエラーを分析して」 | `analyze_console_logs`, `console` |
| 「ゲーム速度を半分にして」 | `control_game_speed` |
| 「パフォーマンスプロファイルを取得して」 | `profile_performance`, `analyze_draw_calls` |
| 「シーンにデバッグ用の線を描画して」 | `debug_draw` |
| 「ユニットテストを実行して」 | `run_tests` |
### 複合的な指示例
**シーン・環境設定系**
- 「ホラーゲーム用のライティングにして。暗めの環境光、点滅するPoint Light、霧を追加して」
- 「夕焼けのシーンを作って。オレンジ色のDirectional Light、レンズフレア、暖色系のポストプロセス」
- 「雨のシーンにして。雨パーティクル、地面に波紋エフェクト、雨音の3Dオーディオを追加」
- 「Assets/Prefabs/にあるTree_01を10個ランダムに配置して。Y=0の平面上に」
- 「Cubeを5x5のグリッド状に並べて。間隔2m、Groundというマテリアルを適用して」
**キャラクター・AI系**
- 「Playerという名前のCubeを作って、WASDで移動、Spaceでジャンプできるようにして。移動速度5、ジャンプ力8で」
- 「Enemyを作って、Playerを見つけたら追いかけて、近づいたら攻撃する。見失ったら元の位置に戻るAIにして」
- 「商人NPCを作って。プレイヤーが近づいたら挨拶、話しかけたらショップUI表示、離れたらお辞儀するようにして」
- 「パトロールする警備員を作って。ウェイポイントを巡回、プレイヤーを見つけたら警報、追跡、見失ったら巡回に戻る」
**カメラ系**
- 「Playerを追いかけるカメラを作って。距離5m、高さ2m、壁があったら自動で回り込むようにして」
- 「ボス戦用のカメラを作って。PlayerとBossの両方をフレームに収めて、距離に応じてズームして」
- 「カットシーン用のカメラを作って。PointAからPointBまで5秒かけて移動、途中でCharacterを見るようにして」
- 「FPSカメラを作って。マウスで視点操作、感度0.5、Y軸反転なし、上下の角度制限-60〜60度」
**エフェクト系**
- 「剣を振った時のエフェクトを作って。白い軌跡、ヒット時に火花、敵に当たったら画面を0.1秒揺らして」
- 「魔法の詠唱エフェクトを作って。足元に青い魔法陣、周囲に浮かぶ光の粒子、詠唱完了時にフラッシュ」
- 「爆発エフェクトを作って。炎、煙、破片、衝撃波、2m以内のオブジェクトを吹き飛ばす」
- 「回復エフェクトを作って。キャラクターの周りに緑の光、上昇するパーティクル、完了時に輪が広がる」
**UI系**
- 「HPバーを作って。左上に配置、赤いバー、ダメージで減少アニメーション、20%以下で点滅」
- 「ダメージ数字を作って。敵の頭上に表示、赤い文字、上に浮かんで消える、クリティカルは黄色で大きく」
- 「ポーズメニューを作って。続ける、設定、タイトルに戻るボタン。ESCで開閉、開いてる間はゲーム停止」
- 「ミニマップを作って。右上に配置、プレイヤーは白い矢印、敵は赤い点、回転は固定で上が北」
**オーディオ系**
- 「足音システムを作って。歩く/走るで音量変化、地面の素材で音が変わる(草、石、木、金属)」
- 「BGMを作って。タイトルはBGM_Title、バトルはBGM_Battle、フェードで切り替え、バトル終了で戻る」
- 「環境音を作って。森は鳥、川のせせらぎ、風。洞窟は反響、水滴。エリアで自動切り替え」
**アニメーション系**
- 「Animator Controllerを作って。Idle、Walk、Runのステートを追加、Speedパラメータで遷移」
- 「Assets/Animations/のIdle.animとWalk.animをPlayerに設定して」
- 「攻撃ステートを追加して。Attack1→Attack2→Attack3のコンボ遷移、Attackトリガーで発動」
- 「アニメーションにイベントを追加して。Attack1の0.5秒地点でOnHitメソッドを呼ぶ」
**天候・時間系**
- 「天候システムを作って。晴れ→曇り→雨→雷雨がランダムで変わる。各天候は5分〜15分続く」
- 「昼夜サイクルを作って。1時間で1日経過、朝6時に日の出、18時に日没、夜は街灯が点く」
- 「雨を降らせて。パーティクル、地面に波紋、濡れた地面の反射、雨音、屋内では聞こえない」
**デバッグ・開発系**
- 「コンソールのエラーを全部見せて、原因と修正方法を教えて」
- 「このシーンのパフォーマンスを分析して。ドローコール、バッチ、FPSを教えて」
- 「Player.csを見せて、UpdateメソッドのGetInputの部分を新しいInput Systemに書き換えて」
- 「プロジェクト内で"OnTriggerEnter"を使ってるスクリプトを全部リストして」
---
## 目次
- [GameObject (11ツール)](#gameobject)
- [Transform (4ツール)](#transform)
- [Camera (13ツール)](#camera)
- [Cinemachine (9ツール)](#cinemachine)
- [Lighting (10ツール)](#lighting)
- [Material (9ツール)](#material)
- [Shader (13ツール)](#shader)
- [VFX (41ツール)](#vfx)
- [Animation (14ツール)](#animation)
- [Audio (14ツール)](#audio)
- [Physics (6ツール)](#physics)
- [UI (15ツール)](#ui)
- [Scene (10ツール)](#scene)
- [Input (13ツール)](#input)
- [Weather (9ツール)](#weather)
- [TimeOfDay (4ツール)](#timeofday)
- [GOAP AI (10ツール)](#goap-ai)
- [AI (3ツール)](#ai)
- [GameSystems (5ツール)](#gamesystems)
- [Scripting (9ツール)](#scripting)
- [Editor (8ツール)](#editor)
- [Screenshot (4ツール)](#screenshot)
- [Monitoring (10ツール)](#monitoring)
- [AssetManagement (12ツール)](#assetmanagement)
- [Optimization (7ツール)](#optimization)
- [Batch (5ツール)](#batch)
- [Build (4ツール)](#build)
- [Package (3ツール)](#package)
- [Debug (6ツール)](#debug)
- [Timeline (2ツール)](#timeline)
- [Utility (26ツール)](#utility)
- [Other (42ツール)](#other)
---
## GameObject
| ツール | 説明 |
|--------|------|
| `unity_create_gameobject` | Unityシーンに新しいGameObjectを作成 |
| `unity_update_gameobject` | 既存のGameObjectのプロパティを更新 |
| `unity_delete_gameobject` | シーンからGameObjectを削除 |
| `unity_add_component` | GameObjectにコンポーネントを追加 |
| `unity_update_component` | コンポーネントのプロパティを更新 |
| `unity_set_active_scene` | マルチシーン編集でアクティブシーンを設定 |
| `unity_get_gameobjects_list` | フィルタ付きでGameObjectリストを取得 |
| `unity_get_gameobject_detail` | 特定のGameObjectの詳細情報を取得 |
| `unity_duplicate_gameobject` | 既存のGameObjectを複製 |
| `unity_find_gameobjects_by_component` | 特定のコンポーネントを持つ全GameObjectを検索 |
| `unity_get_gameobject_details` | GameObjectの詳細情報を取得 |
---
## Transform
| ツール | 説明 |
|--------|------|
| `unity_set_transform` | GameObjectの位置、回転、スケールを設定 |
| `unity_vfx_remove_block` | VFXコンテキストからブロックを削除 |
| `unity_remove_package` | インストール済みUnityパッケージを削除 |
| `unity_move_asset` | アセットを別のフォルダに移動 |
---
## Camera
| ツール | 説明 |
|--------|------|
| `unity_setup_camera` | シーンにカメラをセットアップ |
| `unity_create_virtual_camera` | フォロー/ルックアットターゲット付きCinemachine仮想カメラを作成 |
| `unity_create_freelook_camera` | 三人称オービット用FreeLookカメラを作成 |
| `unity_update_virtual_camera` | 既存のCinemachine仮想カメラの設定を更新 |
| `unity_create_state_driven_camera` | Animator状態に連動するState-Drivenカメラを作成 |
| `unity_create_clear_shot_camera` | 障害物回避付きClear Shotカメラを作成 |
| `unity_create_blend_list_camera` | 優先度付き子カメラを管理するBlend Listカメラを作成 |
| `unity_set_camera_priority` | Cinemachine仮想カメラの優先度を変更 |
| `unity_set_camera_enabled` | Cinemachine仮想カメラを有効/無効化 |
| `unity_create_mixing_camera` | 複数の子カメラをブレンドするMixingカメラを作成 |
| `unity_update_camera_target` | CinemachineカメラのFollow/LookAtターゲットを更新 |
| `unity_get_active_camera_info` | 現在アクティブなCinemachine仮想カメラの情報を取得 |
| `unity_get_camera_info` | シーン内のカメラ情報を取得 |
---
## Cinemachine
| ツール | 説明 |
|--------|------|
| `unity_setup_cinemachine_brain` | カメラにCinemachine Brainをセットアップ |
| `unity_create_dolly_track` | ウェイポイント付きDolly Trackを作成 |
| `unity_add_confiner_extension` | カメラ移動を制限するConfiner拡張を追加 |
| `unity_create_impulse_source` | カメラシェイク用Impulse Sourceを作成 |
| `unity_add_impulse_listener` | シェイク受信用Impulse Listener拡張を追加 |
| `unity_create_target_group` | 複数ターゲット管理用Target Groupを作成 |
| `unity_update_brain_blend_settings` | Cinemachine Brainのデフォルトブレンド設定を更新 |
| `unity_setup_blend_tree` | スムーズなアニメーション遷移用Blend Treeを作成 |
| `unity_setup_advanced_blend_tree` | 複雑なBlend Treeを作成 |
---
## Lighting
| ツール | 説明 |
|--------|------|
| `unity_setup_lighting` | 拡張オプションとプリセットでライティングをセットアップ |
| `unity_setup_lighting_preset` | プロフェッショナルなライティングプリセットを適用 |
| `unity_setup_reflection_probe` | リアルな反射用Reflection Probeをセットアップ |
| `unity_create_light_probe_group` | 動的GI用Light Probe Groupを作成 |
| `unity_setup_volumetric_fog` | 大気的なボリュメトリックフォグを作成 |
| `unity_create_lightning_effect` | フラッシュエフェクト付き雷を作成 |
| `unity_create_skybox_blend` | 昼と夜のSkyboxをブレンド |
| `unity_create_skybox_from_image` | パノラマ/HDRI/6面画像、または通常の風景写真(sphere mode)からSkyboxを作成 |
| `unity_setup_lighting_scenarios` | 異なるムード用ライティングシナリオを作成 |
| `unity_get_lighting_info` | シーン内のライト情報を取得 |
---
## Material
| ツール | 説明 |
|--------|------|
| `unity_create_material` | 新しいマテリアルを作成 |
| `unity_setup_material` | PBRプロパティでマテリアルを作成/変更 |
| `unity_create_advanced_material` | 高度な設定とテクスチャ付きマテリアルを作成 |
| `unity_create_material_property_block` | インスタンスを作成せずにマテリアルプロパティを変更 |
| `unity_vfx_set_color_gradient` | ColorOverLifeブロックにカラーグラデーションを設定 |
| `unity_set_material_property` | 既存マテリアルのプロパティを変更 |
| `unity_get_material_properties` | マテリアルの全プロパティと値を取得 |
| `unity_get_material_details` | マテリアルの詳細情報を取得 |
| `unity_get_material_info` | シーンで使用されているマテリアル情報を取得 |
---
## Shader
| ツール | 説明 |
|--------|------|
| `unity_create_shader_property_animator` | カラー、float、vectorなどのシェーダープロパティをアニメーション |
| `unity_animate_shader_texture` | スクロール、フリップブック、回転テクスチャアニメーションを作成 |
| `unity_create_shader_gradient` | マテリアルにグラデーションエフェクトを適用 |
| `unity_create_shader_graph` | 自然言語でShader Graphを作成 |
| `unity_create_water_material` | 原神品質の物理ベース反射付きウォーターを作成 |
| `unity_create_toon_material` | スタイライズドなトゥーン/セルシェードマテリアルを作成 |
| `unity_create_hair_material` | リアル/スタイライズドな髪マテリアルを作成 |
| `unity_create_eye_material` | リアル/スタイライズドな目マテリアルを作成 |
| `unity_fix_urp_particle_shaders` | URP互換性のためパーティクルマテリアルを修正 |
| `unity_read_shader` | .shaderファイルの内容を読み取り |
| `unity_modify_shader` | 既存の.shaderファイルを変更 |
| `unity_analyze_shader` | シェーダーの構造を分析 |
| `unity_read_shader_graph` | Unity ShaderGraphファイルを読み取り/解析 |
---
## VFX
| ツール | 説明 |
|--------|------|
| `unity_create_visual_effect` | パーティクル、ライトを組み合わせた複雑なビジュアルエフェクトを作成 |
| `unity_create_decal` | ディテール用デカルプロジェクションを作成 |
| `unity_setup_color_grading` | 映画的なカラーグレーディングを適用 |
| `unity_create_lens_flare` | リアルなレンズフレアエフェクトを作成 |
| `unity_create_screen_shake` | ドラマチックなスクリーンシェイクを適用 |
| `unity_create_screen_fade` | フェードイン/アウトトランジションを作成 |
| `unity_create_vignette_effect` | 映画的なビネットフレーミングを追加 |
| `unity_create_chromatic_aberration` | レンズ歪みと色分離を追加 |
| `unity_vfx_set_spawn_rate` | VFX Graphのスポーンレートを設定 |
| `unity_vfx_list_blocks` | VFX Graph内の全コンテキストとブロックをリスト |
| `unity_vfx_get_block_info` | VFXブロックの詳細情報を取得 |
| `unity_create_bloom` | 自動生成シェーダー付きブルームエフェクトを作成 |
| `unity_create_film_grain` | フィルムグレインエフェクトを作成 |
| `unity_create_motion_blur` | モーションブラーエフェクトを作成 |
| `unity_create_depth_of_field` | 被写界深度エフェクトを作成 |
| `unity_create_lens_distortion` | レンズディストーションエフェクトを作成 |
| `unity_setup_urp_settings` | Universal Render Pipeline設定を構成 |
| `unity_setup_hdrp_settings` | High Definition Render Pipeline設定を構成 |
| `unity_setup_post_processing` | ポストプロセッシングエフェクトスタックを構成 |
| `unity_create_vfx_graph` | 高度な機能付きVFXパーティクルエフェクトを作成 |
| `unity_set_vfx_property` | VFX Graphの公開プロパティを変更 |
| `unity_get_vfx_properties` | VFX Graphの全公開プロパティを取得 |
| `unity_trigger_vfx_event` | VFX Graphにイベント送信(再生、停止、カスタム) |
| `unity_vfx_create` | 新しいVFX Graphアセットを作成 |
| `unity_vfx_add_context` | コンテキスト(Spawn、Initialize、Update、Output)を追加 |
| `unity_vfx_add_block` | コンテキストにブロックを追加 |
| `unity_vfx_add_operator` | VFX Graphにオペレーターノードを追加 |
| `unity_vfx_link_contexts` | 2つのコンテキストをリンク |
| `unity_vfx_get_structure` | VFX Graphの構造を取得 |
| `unity_vfx_compile` | VFX Graphをコンパイルして保存 |
| `unity_vfx_get_available_types` | 利用可能な全コンテキスト、ブロック、オペレータータイプをリスト |
| `unity_vfx_add_parameter` | VFX Graphに公開パラメータを追加 |
| `unity_vfx_connect_slots` | 出力スロットを入力スロットに接続 |
| `unity_vfx_set_attribute` | SetAttributeブロックに属性値を設定 |
| `unity_vfx_create_preset` | プリセットから完全なVFX Graphを作成 |
| `unity_vfx_configure_output` | VFX出力コンテキスト設定を構成 |
| `unity_read_vfx_graph` | 既存のVFX Graphを読み取り/分析 |
| `unity_modify_vfx_graph` | 既存のVFX Graphアセットを変更 |
| `unity_analyze_vfx_graph` | VFX Graphアセット構造を分析 |
| `unity_vfx_set_output` | VFX Graphの出力コンテキスト設定を設定 |
| `unity_vfx_set_block_value` | VFXブロックに値を設定 |
---
## Animation
| ツール | 説明 |
|--------|------|
| `unity_create_animation` | GameObject用アニメーションを作成 |
| `unity_create_animator_controller` | 新しいAnimator Controllerを作成 |
| `unity_add_animation_state` | Animator Controllerに新しいステートを追加 |
| `unity_create_animation_clip` | サンプルカーブ付きアニメーションクリップを作成 |
| `unity_add_animation_transition` | アニメーションステート間のトランジションを作成 |
| `unity_setup_animation_layer` | アニメーションレイヤーを追加/構成 |
| `unity_create_animation_event` | アニメーションクリップにイベントを追加 |
| `unity_bake_animation` | ランタイムアニメーションをアニメーションクリップにベイク |
| `unity_import_mixamo_animation` | Mixamoからアニメーションをインポート/セットアップ |
| `unity_organize_animation_assets` | アニメーションファイルを適切なフォルダ構造に整理 |
| `unity_create_animation_layer_mask` | アニメーションレイヤー用アバターマスクを作成 |
| `unity_retarget_animation` | アニメーションを別のアバターにリターゲット |
| `unity_analyze_animation_performance` | アニメーションパフォーマンスを分析/最適化 |
| `unity_get_animation_details` | アニメーションクリップの詳細情報を取得 |
---
## Audio
| ツール | 説明 |
|--------|------|
| `unity_create_audio_mixer` | オーディオミキサーを作成 |
| `unity_create_audio_source` | AudioSourceコンポーネントを作成/構成 |
| `unity_setup_3d_audio` | 3D空間オーディオ設定を構成 |
| `unity_create_audio_clip` | オーディオファイルをインポートまたはプロシージャルオーディオクリップを作成 |
| `unity_setup_audio_effects` | オーディオエフェクトを追加/構成 |
| `unity_create_reverb_zones` | オーディオリバーブゾーンを作成 |
| `unity_setup_audio_occlusion` | オーディオオクルージョンを構成 |
| `unity_create_adaptive_music` | イントロ+ループミュージックシステムを作成 |
| `unity_setup_audio_triggers` | イベントベースオーディオトリガーを構成 |
| `unity_create_sound_pools` | サウンドバリエーションプールを作成 |
| `unity_create_audio_mixing` | リアルタイムオーディオミキシングとダッキングをセットアップ |
| `unity_setup_spatial_audio` | VR/AR用高度な空間オーディオを構成 |
| `unity_create_audio_visualization` | オーディオに反応するビジュアルエフェクトを作成 |
| `unity_get_audio_details` | オーディオクリップの詳細情報を取得 |
---
## Physics
| ツール | 説明 |
|--------|------|
| `unity_setup_physics` | GameObjectまたはグローバルの物理設定をセットアップ |
| `unity_setup_navmesh` | ナビゲーションメッシュをセットアップ |
| `unity_add_collider_extension` | 障害物回避用Collider拡張を追加 |
| `unity_get_physics_settings` | Unity物理設定を取得 |
| `unity_get_physics_info` | シーン内の物理オブジェクト情報を取得 |
| `unity_setup_ui_navigation` | UIナビゲーションシステムを作成 |
---
## UI
| ツール | 説明 |
|--------|------|
| `unity_create_ui` | UnityでUI要素を作成 |
| `unity_capture_ui_element` | 名前で特定のUI要素をキャプチャ |
| `unity_setup_ui_anchors` | アンカーとピボットを自動セットアップ |
| `unity_setup_ui_animation` | UIアニメーションをセットアップ(フェード、スケール、スライド) |
| `unity_create_ui_grid` | UIグリッドレイアウトを作成 |
| `unity_create_ui_notification` | 通知システムを作成 |
| `unity_create_ui_dialog` | モーダルダイアログを作成 |
| `unity_optimize_ui_canvas` | パフォーマンス用にCanvasを最適化 |
| `unity_apply_ui_theme` | UI要素に完全なテーマを適用 |
| `unity_set_ui_colors` | UI要素の色を設定 |
| `unity_style_ui_elements` | UI要素にスタイリングを適用 |
| `unity_add_ui_effects` | UI要素にビジュアルエフェクトを追加 |
| `unity_get_ui_info` | シーン内のUI要素情報を取得 |
| `unity_setup_ui_canvas` | 異なるレンダーモードでUI Canvasを構成 |
| `unity_set_ui_anchor` | UI要素のアンカープリセットを設定 |
---
## Scene
| ツール | 説明 |
|--------|------|
| `unity_manage_scene` | シーン管理操作 |
| `unity_load_scene` | エディタモードでシーンをロード |
| `unity_unload_scene` | エディタからシーンをアンロード |
| `unity_list_all_scenes` | プロジェクト内の全シーンファイルをリスト |
| `unity_add_scene_to_build` | Build Settingsにシーンを追加/削除 |
| `unity_get_scene_info` | 包括的なシーン情報を取得 |
| `unity_get_scene_summary` | 軽量なシーン概要を取得 |
| `unity_get_scene_changes_since` | タイムスタンプ以降のシーン変更を取得 |
| `unity_capture_scene_view` | Scene Viewのスクリーンショットをキャプチャ |
| `unity_pause_scene` | シーンビューを一時停止/再開 |
---
## Input
| ツール | 説明 |
|--------|------|
| `unity_setup_custom_input` | カスタム入力アクションとバインディングを構成 |
| `unity_create_gesture_recognition` | タッチまたはモーション用ジェスチャー認識をセットアップ |
| `unity_setup_haptic_feedback` | ハプティック/バイブレーションフィードバックを構成 |
| `unity_create_input_validation` | 入力検証システムを作成 |
| `unity_setup_accessibility_input` | 入力用アクセシビリティ機能を構成 |
| `unity_create_input_recording` | 入力記録/再生をセットアップ |
| `unity_setup_multitouch` | マルチタッチ入力処理を構成 |
| `unity_create_pinch_zoom` | ピンチトゥズーム機能をセットアップ |
| `unity_setup_swipe_detection` | スワイプジェスチャー検出を構成 |
| `unity_create_drag_drop` | ドラッグ&ドロップ機能をセットアップ |
| `unity_setup_touch_effects` | タッチ用ビジュアルエフェクトを構成 |
| `unity_get_input_settings` | Unity入力設定を取得 |
| `unity_setup_input_system` | テンプレート付きUnity Input Systemをセットアップ |
---
## Weather
| ツール | 説明 |
|--------|------|
| `unity_create_terrain` | Unityでテレインを作成 |
| `unity_modify_terrain` | テレインの高さまたはテクスチャを変更 |
| `unity_create_weather_system` | 完全な天候システムを作成 |
| `unity_set_weather_preset` | 異なる天候プリセットにトランジション |
| `unity_create_rain_effect` | リアルな雨エフェクトを作成 |
| `unity_create_snow_effect` | 降雪エフェクトを作成 |
| `unity_create_wind_effect` | オブジェクトに影響する風エフェクトを作成 |
| `unity_create_thunderstorm` | 完全な雷雨を作成 |
| `unity_get_terrain_info` | テレイン情報を取得 |
---
## TimeOfDay
| ツール | 説明 |
|--------|------|
| `unity_create_time_of_day` | 動的な昼夜サイクルを作成 |
| `unity_set_time_of_day` | 昼夜サイクルの特定の時間を設定 |
| `unity_create_day_night_preset` | プリセットライティング条件を適用 |
| `unity_create_time_event` | 特定の時間にトリガーされるイベントを作成 |
---
## GOAP AI
| ツール | 説明 |
|--------|------|
| `unity_setup_behavior_tree` | ビヘイビアツリーAIシステムをセットアップ |
| `unity_define_goap_goal` | GOAPエージェントのゴールを定義 |
| `unity_create_goap_action` | GOAPエージェントのアクションを作成 |
| `unity_generate_goap_action_set` | エージェントタイプに基づきアクションセットを自動生成 |
| `unity_setup_goap_world_state` | GOAPプランニング用ワールドステートを構成 |
| `unity_debug_goap_decisions` | GOAP判断を可視化/デバッグ |
| `unity_optimize_goap_performance` | GOAPエージェントパフォーマンスを最適化 |
| `unity_create_goap_agent` | GOAP AIエージェントを作成 |
| `unity_define_behavior_language` | 自然言語でAIビヘイビアを定義 |
| `unity_create_goap_template` | プロフェッショナルテンプレートからGOAP AIを作成 |
---
## AI
| ツール | 説明 |
|--------|------|
| `unity_setup_ml_agent` | 機械学習エージェントをセットアップ |
| `unity_create_neural_network` | ニューラルネットワークシステムを作成 |
| `unity_create_ai_pathfinding` | A*アルゴリズムでAIパスファインディングを作成 |
---
## GameSystems
| ツール | 説明 |
|--------|------|
| `unity_create_game_controller` | 異なるゲームタイプ用プレイヤーコントローラーを作成 |
| `unity_create_state_machine` | キャラクターまたはゲーム状態用ステートマシンを作成 |
| `unity_setup_inventory_system` | UI付きインベントリシステムを作成 |
| `unity_create_game_template` | 完全なゲームテンプレートを作成 |
| `unity_quick_prototype` | 素早くプレイ可能なプロトタイプを作成 |
---
## Scripting
| ツール | 説明 |
|--------|------|
| `unity_grep_scripts` | スクリプトファイル全体でパターンを検索 |
| `unity_read_script_range` | スクリプトの特定行範囲を読み取り |
| `unity_list_script_files` | globパターンサポート付きでスクリプトファイルをリスト |
| `unity_analyze_script` | 品質問題のための包括的なスクリプト分析 |
| `unity_modify_script` | 既存のUnityスクリプトを編集 |
| `unity_edit_script_line` | Unityスクリプトの特定行を編集 |
| `unity_add_script_method` | Unityスクリプトに新しいメソッドを追加 |
| `unity_update_script_variable` | 変数宣言または値を更新 |
| `unity_read_script` | Unityスクリプトファイルの内容を読み取り |
---
## Editor
| ツール | 説明 |
|--------|------|
| `unity_force_refresh_assets` | Unityにアセットデータベースの更新を強制 |
| `unity_invoke_context_menu` | コンポーネントの[ContextMenu]メソッドを呼び出し |
| `unity_execute_menu_item` | Unity Editorメニュー項目を実行 |
| `unity_get_inspector_info` | 詳細なインスペクター情報を取得 |
| `unity_get_selected_object_info` | 現在選択中のGameObject情報を取得 |
| `unity_get_component_details` | 特定コンポーネントの詳細を取得 |
| `unity_console` | Unityコンソールログ操作 |
| `unity_analyze_console_logs` | Unityコンソールログの詳細分析 |
---
## Screenshot
| ツール | 説明 |
|--------|------|
| `unity_capture_game_view` | Canvas/UI含むGame Viewをキャプチャ |
| `unity_capture_region` | 特定の領域をキャプチャ |
| `unity_get_screenshot_result` | スクリーンショットキャプチャ結果を取得 |
| `unity_capture_grid` | Game Viewをグリッドとして分割キャプチャ |
---
## Monitoring
| ツール | 説明 |
|--------|------|
| `unity_get_operation_history` | Unity操作履歴を取得 |
| `unity_create_checkpoint` | 後で復元するチェックポイントを作成 |
| `unity_restore_checkpoint` | 以前に作成したチェックポイントを復元 |
| `unity_monitor_play_state` | Unityプレイモード状態変更を監視 |
| `unity_monitor_file_changes` | プロジェクト内のファイル変更を監視 |
| `unity_monitor_compile` | スクリプトコンパイルイベントを監視 |
| `unity_subscribe_events` | Unityイベントを購読 |
| `unity_get_events` | 最近のUnityイベントを取得 |
| `unity_get_monitoring_status` | 現在の監視状態を取得 |
| `unity_monitor_runtime_errors` | リアルタイムランタイムエラー監視 |
---
## AssetManagement
| ツール | 説明 |
|--------|------|
| `unity_get_asset_dependencies` | アセットの全依存関係を取得 |
| `unity_check_folder` | フォルダの存在を確認 |
| `unity_create_folder` | 新しいフォルダを作成 |
| `unity_list_folders` | パス内のフォルダをリスト |
| `unity_cleanup_empty_objects` | シーンから空のGameObjectを削除 |
| `unity_rename_asset` | アセットファイルの名前を変更 |
| `unity_delete_asset` | プロジェクトからアセットを削除 |
| `unity_create_project_snapshot` | プロジェクト状態のスナップショットを作成 |
| `unity_analyze_dependencies` | アセット依存関係を分析/可視化 |
| `unity_export_project_structure` | プロジェクトフォルダ構造をエクスポート |
| `unity_validate_naming_conventions` | アセットが命名規則に従っているか確認 |
| `unity_auto_organize_folders` | アセットを自動的にフォルダに整理 |
---
## Optimization
| ツール | 説明 |
|--------|------|
| `unity_optimize_textures_batch` | テクスチャインポート設定を一括最適化 |
| `unity_analyze_draw_calls` | ドローコール最適化機会を分析 |
| `unity_estimate_build_size` | 異なるプラットフォーム用ビルドサイズを推定 |
| `unity_performance_report` | 包括的なパフォーマンスレポートを生成 |
| `unity_generate_lod` | LODグループを自動生成 |
| `unity_auto_atlas_textures` | テクスチャアトラスを自動作成 |
| `unity_check_best_practices` | ベストプラクティス準拠のためスクリプトを分析 |
---
## Batch
| ツール | 説明 |
|--------|------|
| `unity_extract_all_text` | ローカライゼーション用に全テキストコンテンツを抽出 |
| `unity_batch_rename` | 複数のアセットを一括リネーム |
| `unity_batch_prefab_update` | 複数のプレハブを変更で更新 |
| `unity_batch_material_apply` | 複数のオブジェクトにマテリアルを適用 |
| `unity_batch_prefab_create` | 複数のオブジェクトからプレハブを作成 |
---
## Build
| ツール | 説明 |
|--------|------|
| `unity_get_build_settings` | Unityビルド設定を取得 |
| `unity_get_player_settings` | Unityプレイヤー設定を取得 |
| `unity_get_quality_settings` | Unity品質設定を取得 |
| `unity_get_project_summary` | 全体的なプロジェクトサマリーを取得 |
---
## Package
| ツール | 説明 |
|--------|------|
| `unity_list_packages` | インストール済み全Unityパッケージをリスト |
| `unity_install_package` | Unityパッケージをインストール |
| `unity_check_package` | パッケージがインストール済みか確認 |
---
## Debug
| ツール | 説明 |
|--------|------|
| `unity_control_game_speed` | Unityゲーム速度(タイムスケール)を制御 |
| `unity_profile_performance` | Unityパフォーマンスプロファイリングデータを取得 |
| `unity_debug_draw` | Unityシーンにデバッグシェイプを描画 |
| `unity_run_tests` | Unity Test Runnerテストを実行 |
| `unity_manage_breakpoints` | デバッグブレークポイントを管理 |
| `unity_debug_bt` | ビヘイビアツリーのデバッグ情報を取得 |
---
## Timeline
| ツール | 説明 |
|--------|------|
| `unity_setup_avatar` | 3Dモデル用アバターを構成 |
| `unity_create_timeline` | シネマティクス用Unity Timelineを作成 |
---
## Utility
| ツール | 説明 |
|--------|------|
| `unity_add_target_to_group` | Cinemachine Target Groupにターゲットを追加 |
| `unity_create_script` | 新しいC#スクリプトを作成 |
| `unity_create_particle_system` | パーティクルシステムを作成 |
| `unity_create_particle_preset` | 高度なパーティクルエフェクトを作成 |
| `unity_place_objects` | パターンで複数のオブジェクトを配置 |
| `unity_undo_operation` | 最後のUnity操作を取り消し |
| `unity_redo_operation` | 以前に取り消した操作をやり直し |
| `unity_list_assets` | プロジェクト内のアセットをリスト |
| `unity_search_prefabs_by_component` | 特定コンポーネントを持つプレハブを検索 |
| `unity_find_material_usage` | マテリアルを使用している全オブジェクトを検索 |
| `unity_find_texture_usage` | テクスチャを使用している全マテリアルを検索 |
| `unity_find_missing_references` | プロジェクト内の欠落参照を検索 |
| `unity_group_gameobjects` | 複数のGameObjectを親の下にグループ化 |
| `unity_batch_import_settings` | 複数のアセットにインポート設定を適用 |
| `unity_find_unused_assets` | プロジェクトで参照されていないアセットを検索 |
| `unity_create_responsive_ui` | レスポンシブUIコンテナを作成 |
| `unity_setup_scroll_view` | 完全なスクロールビューを作成 |
| `unity_setup_safe_area` | モバイル用セーフエリアをセットアップ |
| `unity_get_asset_file_info` | アセットのファイルシステム情報を取得 |
| `unity_get_asset_import_settings` | アセットのインポート設定を取得 |
| `unity_search_code` | プロジェクト内でコードパターンを検索 |
| `unity_auto_attach_ui` | UI要素をコンポーネントフィールドに自動アタッチ |
| `unity_create_prefab` | GameObjectからプレハブを作成 |
| `unity_read_particle_system` | 全Particle Systemプロパティを読み取り |
| `unity_modify_particle_system` | Particle Systemプロパティを変更 |
---
## Other
以下を含む42の追加ツール:
- ビヘイビアツリー (`unity_create_bt_agent`, `unity_add_bt_node` など)
- ウォーターシステム (`unity_create_ocean_system`, `unity_add_buoyancy` など)
- HDRP Water (`unity_create_hdrp_water`, `unity_set_hdrp_water_property`)
- 特殊エフェクト (`unity_create_caustics`, `unity_trigger_dissolve` など)
- ランタイムステータス (`unity_get_runtime_status`, `unity_get_performance_metrics` など)
- 通信 (`unity_send_chat_response`, `unity_check_messages`)
---
## バージョン情報
- **ツール数**: 350
- **バージョン**: 1.2.0
- **最終更新**: 2026-01-19
---
*tool-registry.json から自動生成*
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 50037a3a3abff4d43801a3e165d50beb
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Documentation/TOOLS_JP.md
uploadId: 920982
@@ -1,779 +0,0 @@
# Synaptic AI Pro - ツールリファレンス (350ツール)
Synaptic AI Pro for Unity で利用可能な全MCPツールの完全リファレンスです。
---
## 実用的な指示例
AIに自然言語で指示するだけで、複数のツールが自動的に連携して実行されます。
### ゲーム制作
| 指示例 | 使用されるツール |
|--------|------------------|
| 「3Dプラットフォーマーゲームのプロトタイプを作って」 | `create_game_template`, `create_gameobject`, `setup_physics`, `create_virtual_camera` |
| 「FPSゲームの基本セットアップをして」 | `quick_prototype`, `create_game_controller`, `setup_input_system` |
| 「RPGのインベントリシステムを作って」 | `setup_inventory_system`, `create_ui`, `create_script` |
| 「レースゲームのコースを作って」 | `create_terrain`, `place_objects`, `create_dolly_track` |
| 「パズルゲームのグリッドを作って」 | `create_ui_grid`, `create_state_machine`, `place_objects` |
### シーン構築
| 指示例 | 使用されるツール |
|--------|------------------|
| 「森の中の夜のシーンを作って」 | `create_terrain`, `place_objects`, `create_time_of_day`, `setup_lighting_preset` |
| 「雨の降る街のシーンを作って」 | `create_rain_effect`, `setup_volumetric_fog`, `create_audio_source`, `setup_lighting` |
| 「宇宙空間のシーンを作って、星と惑星を配置して」 | `create_skybox_from_image`, `place_objects`, `setup_lighting_preset`, `create_lens_flare` |
| 「水中のシーンを作って」 | `create_ocean_system`, `setup_post_processing`, `create_caustics`, `setup_color_grading` |
| 「砂漠の昼間のシーンを作って」 | `create_terrain`, `setup_lighting_preset`, `create_wind_effect`, `setup_volumetric_fog` |
### カメラワーク
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プレイヤーを追いかける三人称カメラを作って」 | `create_freelook_camera`, `setup_cinemachine_brain` |
| 「カットシーン用のカメラパスを作って」 | `create_dolly_track`, `create_virtual_camera`, `create_timeline` |
| 「複数のキャラクターを映すカメラを作って」 | `create_target_group`, `create_virtual_camera`, `add_target_to_group` |
| 「壁を貫通しないカメラを設定して」 | `create_virtual_camera`, `add_collider_extension`, `add_confiner_extension` |
| 「爆発時にカメラを揺らして」 | `create_impulse_source`, `add_impulse_listener` |
| 「会話シーン用に2人のキャラを映すカメラを作って」 | `create_target_group`, `create_clear_shot_camera` |
### ライティング・天候
| 指示例 | 使用されるツール |
|--------|------------------|
| 「24時間の昼夜サイクルを作って」 | `create_time_of_day`, `create_skybox_blend`, `setup_lighting_scenarios` |
| 「夕焼けのライティングにして」 | `setup_lighting_preset`, `setup_color_grading`, `create_lens_flare` |
| 「雷雨を作って稲妻も光らせて」 | `create_thunderstorm`, `create_lightning_effect`, `create_screen_shake` |
| 「霧の出た早朝のシーンを作って」 | `setup_volumetric_fog`, `setup_lighting_preset`, `create_time_of_day` |
| 「スタジオライティングをセットアップして」 | `setup_lighting_preset`, `setup_reflection_probe`, `create_light_probe_group` |
### VFX・エフェクト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「炎のパーティクルエフェクトを作って」 | `create_vfx_graph`, `vfx_create_preset` |
| 「魔法のエフェクトを作って(青い光とパーティクル)」 | `create_visual_effect`, `create_particle_preset`, `setup_post_processing` |
| 「ブルームとビネットを追加して映画的な見た目にして」 | `create_bloom`, `create_vignette_effect`, `setup_color_grading` |
| 「キャラクターがダメージを受けた時の画面エフェクトを作って」 | `create_screen_shake`, `create_chromatic_aberration`, `create_vignette_effect` |
| 「剣を振った時のトレイルエフェクトを作って」 | `create_vfx_graph`, `vfx_add_context`, `vfx_add_block` |
| 「被写界深度で背景をぼかして」 | `create_depth_of_field`, `setup_post_processing` |
### UI
| 指示例 | 使用されるツール |
|--------|------------------|
| 「HPバーとマナバーのUIを作って」 | `create_ui`, `setup_ui_animation`, `set_ui_colors` |
| 「メインメニュー画面を作って」 | `create_ui`, `create_ui_grid`, `setup_ui_animation`, `apply_ui_theme` |
| 「ダイアログボックスを作って」 | `create_ui_dialog`, `setup_ui_animation` |
| 「モバイル用のジョイスティックUIを作って」 | `create_ui`, `setup_safe_area`, `setup_touch_effects` |
| 「インベントリのグリッドUIを作って」 | `create_ui_grid`, `setup_scroll_view`, `create_drag_drop` |
| 「通知ポップアップシステムを作って」 | `create_ui_notification`, `setup_ui_animation` |
| 「レスポンシブなUIレイアウトを作って」 | `create_responsive_ui`, `setup_ui_anchors`, `setup_safe_area` |
### オーディオ
| 指示例 | 使用されるツール |
|--------|------------------|
| 「BGMシステムを作って、イントロとループを設定して」 | `create_adaptive_music`, `create_audio_mixer` |
| 「3D空間オーディオを設定して」 | `create_audio_source`, `setup_3d_audio`, `setup_audio_occlusion` |
| 「洞窟のリバーブを設定して」 | `create_reverb_zones`, `setup_audio_effects` |
| 「足音のランダムバリエーションを作って」 | `create_sound_pools`, `setup_audio_triggers` |
| 「オーディオビジュアライザーを作って」 | `create_audio_visualization`, `create_vfx_graph` |
### AI・キャラクター
| 指示例 | 使用されるツール |
|--------|------------------|
| 「敵AIを作って、プレイヤーを追いかけて攻撃するようにして」 | `create_goap_agent`, `create_goap_action`, `define_goap_goal` |
| 「NPCのパトロール行動を作って」 | `setup_behavior_tree`, `create_ai_pathfinding`, `setup_navmesh` |
| 「村人AIを作って(巡回、会話、逃走)」 | `create_goap_template`, `setup_goap_world_state` |
| 「ボスAIを作って複数の攻撃パターンを持たせて」 | `create_state_machine`, `create_goap_agent`, `create_animation` |
### アニメーション
| 指示例 | 使用されるツール |
|--------|------------------|
| 「キャラクターのアイドル→歩行→走行のアニメーション遷移を作って」 | `create_animator_controller`, `add_animation_state`, `add_animation_transition` |
| 「ブレンドツリーで移動アニメーションを作って」 | `setup_blend_tree`, `setup_advanced_blend_tree` |
| 「攻撃アニメーションにヒットエフェクトのイベントを追加して」 | `create_animation_event`, `create_visual_effect` |
| 「Mixamoのアニメーションをインポートして設定して」 | `import_mixamo_animation`, `setup_avatar`, `retarget_animation` |
### マテリアル・シェーダー
| 指示例 | 使用されるツール |
|--------|------------------|
| 「原神風の水面シェーダーを作って」 | `create_water_material`, `create_ocean_system` |
| 「トゥーンシェーダーのキャラクターマテリアルを作って」 | `create_toon_material`, `create_shader_graph` |
| 「発光するマテリアルを作って」 | `create_advanced_material`, `set_material_property` |
| 「ディゾルブエフェクトを作って」 | `create_shader_graph`, `trigger_dissolve` |
| 「ホログラムシェーダーを作って」 | `create_shader_graph`, `create_shader_property_animator` |
### スクリプト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プレイヤーの移動スクリプトを作って」 | `create_script`, `read_script`, `modify_script` |
| 「このスクリプトにジャンプ機能を追加して」 | `read_script`, `add_script_method`, `modify_script` |
| 「コード内の"Player"を"Character"に全置換して」 | `grep_scripts`, `modify_script` |
| 「このスクリプトの品質を分析して」 | `analyze_script`, `check_best_practices` |
### プロジェクト管理
| 指示例 | 使用されるツール |
|--------|------------------|
| 「プロジェクトの未使用アセットを見つけて」 | `find_unused_assets`, `analyze_dependencies` |
| 「テクスチャを一括最適化して」 | `optimize_textures_batch`, `auto_atlas_textures` |
| 「アセットを種類別にフォルダ整理して」 | `auto_organize_folders`, `batch_rename` |
| 「ビルドサイズを見積もって」 | `estimate_build_size`, `performance_report` |
| 「欠落参照を見つけて」 | `find_missing_references`, `analyze_dependencies` |
### デバッグ・テスト
| 指示例 | 使用されるツール |
|--------|------------------|
| 「コンソールのエラーを分析して」 | `analyze_console_logs`, `console` |
| 「ゲーム速度を半分にして」 | `control_game_speed` |
| 「パフォーマンスプロファイルを取得して」 | `profile_performance`, `analyze_draw_calls` |
| 「シーンにデバッグ用の線を描画して」 | `debug_draw` |
| 「ユニットテストを実行して」 | `run_tests` |
### 複合的な指示例
**シーン・環境設定系**
- 「ホラーゲーム用のライティングにして。暗めの環境光、点滅するPoint Light、霧を追加して」
- 「夕焼けのシーンを作って。オレンジ色のDirectional Light、レンズフレア、暖色系のポストプロセス」
- 「雨のシーンにして。雨パーティクル、地面に波紋エフェクト、雨音の3Dオーディオを追加」
- 「Assets/Prefabs/にあるTree_01を10個ランダムに配置して。Y=0の平面上に」
- 「Cubeを5x5のグリッド状に並べて。間隔2m、Groundというマテリアルを適用して」
**キャラクター・AI系**
- 「Playerという名前のCubeを作って、WASDで移動、Spaceでジャンプできるようにして。移動速度5、ジャンプ力8で」
- 「Enemyを作って、Playerを見つけたら追いかけて、近づいたら攻撃する。見失ったら元の位置に戻るAIにして」
- 「商人NPCを作って。プレイヤーが近づいたら挨拶、話しかけたらショップUI表示、離れたらお辞儀するようにして」
- 「パトロールする警備員を作って。ウェイポイントを巡回、プレイヤーを見つけたら警報、追跡、見失ったら巡回に戻る」
**カメラ系**
- 「Playerを追いかけるカメラを作って。距離5m、高さ2m、壁があったら自動で回り込むようにして」
- 「ボス戦用のカメラを作って。PlayerとBossの両方をフレームに収めて、距離に応じてズームして」
- 「カットシーン用のカメラを作って。PointAからPointBまで5秒かけて移動、途中でCharacterを見るようにして」
- 「FPSカメラを作って。マウスで視点操作、感度0.5、Y軸反転なし、上下の角度制限-60〜60度」
**エフェクト系**
- 「剣を振った時のエフェクトを作って。白い軌跡、ヒット時に火花、敵に当たったら画面を0.1秒揺らして」
- 「魔法の詠唱エフェクトを作って。足元に青い魔法陣、周囲に浮かぶ光の粒子、詠唱完了時にフラッシュ」
- 「爆発エフェクトを作って。炎、煙、破片、衝撃波、2m以内のオブジェクトを吹き飛ばす」
- 「回復エフェクトを作って。キャラクターの周りに緑の光、上昇するパーティクル、完了時に輪が広がる」
**UI系**
- 「HPバーを作って。左上に配置、赤いバー、ダメージで減少アニメーション、20%以下で点滅」
- 「ダメージ数字を作って。敵の頭上に表示、赤い文字、上に浮かんで消える、クリティカルは黄色で大きく」
- 「ポーズメニューを作って。続ける、設定、タイトルに戻るボタン。ESCで開閉、開いてる間はゲーム停止」
- 「ミニマップを作って。右上に配置、プレイヤーは白い矢印、敵は赤い点、回転は固定で上が北」
**オーディオ系**
- 「足音システムを作って。歩く/走るで音量変化、地面の素材で音が変わる(草、石、木、金属)」
- 「BGMを作って。タイトルはBGM_Title、バトルはBGM_Battle、フェードで切り替え、バトル終了で戻る」
- 「環境音を作って。森は鳥、川のせせらぎ、風。洞窟は反響、水滴。エリアで自動切り替え」
**アニメーション系**
- 「キャラクターのアニメーターを作って。待機、歩行、走行、ジャンプ、着地。SpeedとIsGroundedで制御」
- 「攻撃コンボを作って。クリック1回で斬り1、連打で斬り2→斬り3。2秒操作なしでリセット」
- 「ドアのアニメーションを作って。プレイヤーが近づいて開くボタンで開閉、開くのに1秒、閉まるのに0.5秒」
**天候・時間系**
- 「天候システムを作って。晴れ→曇り→雨→雷雨がランダムで変わる。各天候は5分〜15分続く」
- 「昼夜サイクルを作って。1時間で1日経過、朝6時に日の出、18時に日没、夜は街灯が点く」
- 「雨を降らせて。パーティクル、地面に波紋、濡れた地面の反射、雨音、屋内では聞こえない」
**デバッグ・開発系**
- 「コンソールのエラーを全部見せて、原因と修正方法を教えて」
- 「このシーンのパフォーマンスを分析して。ドローコール、バッチ、FPSを教えて」
- 「Player.csを見せて、UpdateメソッドのGetInputの部分を新しいInput Systemに書き換えて」
- 「プロジェクト内で"OnTriggerEnter"を使ってるスクリプトを全部リストして」
---
## 目次
- [GameObject (11ツール)](#gameobject)
- [Transform (4ツール)](#transform)
- [Camera (13ツール)](#camera)
- [Cinemachine (9ツール)](#cinemachine)
- [Lighting (10ツール)](#lighting)
- [Material (9ツール)](#material)
- [Shader (13ツール)](#shader)
- [VFX (41ツール)](#vfx)
- [Animation (14ツール)](#animation)
- [Audio (14ツール)](#audio)
- [Physics (6ツール)](#physics)
- [UI (15ツール)](#ui)
- [Scene (10ツール)](#scene)
- [Input (13ツール)](#input)
- [Weather (9ツール)](#weather)
- [TimeOfDay (4ツール)](#timeofday)
- [GOAP AI (10ツール)](#goap-ai)
- [AI (3ツール)](#ai)
- [GameSystems (5ツール)](#gamesystems)
- [Scripting (9ツール)](#scripting)
- [Editor (8ツール)](#editor)
- [Screenshot (4ツール)](#screenshot)
- [Monitoring (10ツール)](#monitoring)
- [AssetManagement (12ツール)](#assetmanagement)
- [Optimization (7ツール)](#optimization)
- [Batch (5ツール)](#batch)
- [Build (4ツール)](#build)
- [Package (3ツール)](#package)
- [Debug (6ツール)](#debug)
- [Timeline (2ツール)](#timeline)
- [Utility (26ツール)](#utility)
- [Other (42ツール)](#other)
---
## GameObject
| ツール | 説明 |
|--------|------|
| `unity_create_gameobject` | Unityシーンに新しいGameObjectを作成 |
| `unity_update_gameobject` | 既存のGameObjectのプロパティを更新 |
| `unity_delete_gameobject` | シーンからGameObjectを削除 |
| `unity_add_component` | GameObjectにコンポーネントを追加 |
| `unity_update_component` | コンポーネントのプロパティを更新 |
| `unity_set_active_scene` | マルチシーン編集でアクティブシーンを設定 |
| `unity_get_gameobjects_list` | フィルタ付きでGameObjectリストを取得 |
| `unity_get_gameobject_detail` | 特定のGameObjectの詳細情報を取得 |
| `unity_duplicate_gameobject` | 既存のGameObjectを複製 |
| `unity_find_gameobjects_by_component` | 特定のコンポーネントを持つ全GameObjectを検索 |
| `unity_get_gameobject_details` | GameObjectの詳細情報を取得 |
---
## Transform
| ツール | 説明 |
|--------|------|
| `unity_set_transform` | GameObjectの位置、回転、スケールを設定 |
| `unity_vfx_remove_block` | VFXコンテキストからブロックを削除 |
| `unity_remove_package` | インストール済みUnityパッケージを削除 |
| `unity_move_asset` | アセットを別のフォルダに移動 |
---
## Camera
| ツール | 説明 |
|--------|------|
| `unity_setup_camera` | シーンにカメラをセットアップ |
| `unity_create_virtual_camera` | フォロー/ルックアットターゲット付きCinemachine仮想カメラを作成 |
| `unity_create_freelook_camera` | 三人称オービット用FreeLookカメラを作成 |
| `unity_update_virtual_camera` | 既存のCinemachine仮想カメラの設定を更新 |
| `unity_create_state_driven_camera` | Animator状態に連動するState-Drivenカメラを作成 |
| `unity_create_clear_shot_camera` | 障害物回避付きClear Shotカメラを作成 |
| `unity_create_blend_list_camera` | 優先度付き子カメラを管理するBlend Listカメラを作成 |
| `unity_set_camera_priority` | Cinemachine仮想カメラの優先度を変更 |
| `unity_set_camera_enabled` | Cinemachine仮想カメラを有効/無効化 |
| `unity_create_mixing_camera` | 複数の子カメラをブレンドするMixingカメラを作成 |
| `unity_update_camera_target` | CinemachineカメラのFollow/LookAtターゲットを更新 |
| `unity_get_active_camera_info` | 現在アクティブなCinemachine仮想カメラの情報を取得 |
| `unity_get_camera_info` | シーン内のカメラ情報を取得 |
---
## Cinemachine
| ツール | 説明 |
|--------|------|
| `unity_setup_cinemachine_brain` | カメラにCinemachine Brainをセットアップ |
| `unity_create_dolly_track` | ウェイポイント付きDolly Trackを作成 |
| `unity_add_confiner_extension` | カメラ移動を制限するConfiner拡張を追加 |
| `unity_create_impulse_source` | カメラシェイク用Impulse Sourceを作成 |
| `unity_add_impulse_listener` | シェイク受信用Impulse Listener拡張を追加 |
| `unity_create_target_group` | 複数ターゲット管理用Target Groupを作成 |
| `unity_update_brain_blend_settings` | Cinemachine Brainのデフォルトブレンド設定を更新 |
| `unity_setup_blend_tree` | スムーズなアニメーション遷移用Blend Treeを作成 |
| `unity_setup_advanced_blend_tree` | 複雑なBlend Treeを作成 |
---
## Lighting
| ツール | 説明 |
|--------|------|
| `unity_setup_lighting` | 拡張オプションとプリセットでライティングをセットアップ |
| `unity_setup_lighting_preset` | プロフェッショナルなライティングプリセットを適用 |
| `unity_setup_reflection_probe` | リアルな反射用Reflection Probeをセットアップ |
| `unity_create_light_probe_group` | 動的GI用Light Probe Groupを作成 |
| `unity_setup_volumetric_fog` | 大気的なボリュメトリックフォグを作成 |
| `unity_create_lightning_effect` | フラッシュエフェクト付き雷を作成 |
| `unity_create_skybox_blend` | 昼と夜のSkyboxをブレンド |
| `unity_create_skybox_from_image` | パノラマ/HDRI画像または6面画像からSkyboxを作成 |
| `unity_setup_lighting_scenarios` | 異なるムード用ライティングシナリオを作成 |
| `unity_get_lighting_info` | シーン内のライト情報を取得 |
---
## Material
| ツール | 説明 |
|--------|------|
| `unity_create_material` | 新しいマテリアルを作成 |
| `unity_setup_material` | PBRプロパティでマテリアルを作成/変更 |
| `unity_create_advanced_material` | 高度な設定とテクスチャ付きマテリアルを作成 |
| `unity_create_material_property_block` | インスタンスを作成せずにマテリアルプロパティを変更 |
| `unity_vfx_set_color_gradient` | ColorOverLifeブロックにカラーグラデーションを設定 |
| `unity_set_material_property` | 既存マテリアルのプロパティを変更 |
| `unity_get_material_properties` | マテリアルの全プロパティと値を取得 |
| `unity_get_material_details` | マテリアルの詳細情報を取得 |
| `unity_get_material_info` | シーンで使用されているマテリアル情報を取得 |
---
## Shader
| ツール | 説明 |
|--------|------|
| `unity_create_shader_property_animator` | カラー、float、vectorなどのシェーダープロパティをアニメーション |
| `unity_animate_shader_texture` | スクロール、フリップブック、回転テクスチャアニメーションを作成 |
| `unity_create_shader_gradient` | マテリアルにグラデーションエフェクトを適用 |
| `unity_create_shader_graph` | 自然言語でShader Graphを作成 |
| `unity_create_water_material` | 原神品質の物理ベース反射付きウォーターを作成 |
| `unity_create_toon_material` | スタイライズドなトゥーン/セルシェードマテリアルを作成 |
| `unity_create_hair_material` | リアル/スタイライズドな髪マテリアルを作成 |
| `unity_create_eye_material` | リアル/スタイライズドな目マテリアルを作成 |
| `unity_fix_urp_particle_shaders` | URP互換性のためパーティクルマテリアルを修正 |
| `unity_read_shader` | .shaderファイルの内容を読み取り |
| `unity_modify_shader` | 既存の.shaderファイルを変更 |
| `unity_analyze_shader` | シェーダーの構造を分析 |
| `unity_read_shader_graph` | Unity ShaderGraphファイルを読み取り/解析 |
---
## VFX
| ツール | 説明 |
|--------|------|
| `unity_create_visual_effect` | パーティクル、ライトを組み合わせた複雑なビジュアルエフェクトを作成 |
| `unity_create_decal` | ディテール用デカルプロジェクションを作成 |
| `unity_setup_color_grading` | 映画的なカラーグレーディングを適用 |
| `unity_create_lens_flare` | リアルなレンズフレアエフェクトを作成 |
| `unity_create_screen_shake` | ドラマチックなスクリーンシェイクを適用 |
| `unity_create_screen_fade` | フェードイン/アウトトランジションを作成 |
| `unity_create_vignette_effect` | 映画的なビネットフレーミングを追加 |
| `unity_create_chromatic_aberration` | レンズ歪みと色分離を追加 |
| `unity_vfx_set_spawn_rate` | VFX Graphのスポーンレートを設定 |
| `unity_vfx_list_blocks` | VFX Graph内の全コンテキストとブロックをリスト |
| `unity_vfx_get_block_info` | VFXブロックの詳細情報を取得 |
| `unity_create_bloom` | 自動生成シェーダー付きブルームエフェクトを作成 |
| `unity_create_film_grain` | フィルムグレインエフェクトを作成 |
| `unity_create_motion_blur` | モーションブラーエフェクトを作成 |
| `unity_create_depth_of_field` | 被写界深度エフェクトを作成 |
| `unity_create_lens_distortion` | レンズディストーションエフェクトを作成 |
| `unity_setup_urp_settings` | Universal Render Pipeline設定を構成 |
| `unity_setup_hdrp_settings` | High Definition Render Pipeline設定を構成 |
| `unity_setup_post_processing` | ポストプロセッシングエフェクトスタックを構成 |
| `unity_create_vfx_graph` | 高度な機能付きVFXパーティクルエフェクトを作成 |
| `unity_set_vfx_property` | VFX Graphの公開プロパティを変更 |
| `unity_get_vfx_properties` | VFX Graphの全公開プロパティを取得 |
| `unity_trigger_vfx_event` | VFX Graphにイベント送信(再生、停止、カスタム) |
| `unity_vfx_create` | 新しいVFX Graphアセットを作成 |
| `unity_vfx_add_context` | コンテキスト(Spawn、Initialize、Update、Output)を追加 |
| `unity_vfx_add_block` | コンテキストにブロックを追加 |
| `unity_vfx_add_operator` | VFX Graphにオペレーターノードを追加 |
| `unity_vfx_link_contexts` | 2つのコンテキストをリンク |
| `unity_vfx_get_structure` | VFX Graphの構造を取得 |
| `unity_vfx_compile` | VFX Graphをコンパイルして保存 |
| `unity_vfx_get_available_types` | 利用可能な全コンテキスト、ブロック、オペレータータイプをリスト |
| `unity_vfx_add_parameter` | VFX Graphに公開パラメータを追加 |
| `unity_vfx_connect_slots` | 出力スロットを入力スロットに接続 |
| `unity_vfx_set_attribute` | SetAttributeブロックに属性値を設定 |
| `unity_vfx_create_preset` | プリセットから完全なVFX Graphを作成 |
| `unity_vfx_configure_output` | VFX出力コンテキスト設定を構成 |
| `unity_read_vfx_graph` | 既存のVFX Graphを読み取り/分析 |
| `unity_modify_vfx_graph` | 既存のVFX Graphアセットを変更 |
| `unity_analyze_vfx_graph` | VFX Graphアセット構造を分析 |
| `unity_vfx_set_output` | VFX Graphの出力コンテキスト設定を設定 |
| `unity_vfx_set_block_value` | VFXブロックに値を設定 |
---
## Animation
| ツール | 説明 |
|--------|------|
| `unity_create_animation` | GameObject用アニメーションを作成 |
| `unity_create_animator_controller` | 新しいAnimator Controllerを作成 |
| `unity_add_animation_state` | Animator Controllerに新しいステートを追加 |
| `unity_create_animation_clip` | サンプルカーブ付きアニメーションクリップを作成 |
| `unity_add_animation_transition` | アニメーションステート間のトランジションを作成 |
| `unity_setup_animation_layer` | アニメーションレイヤーを追加/構成 |
| `unity_create_animation_event` | アニメーションクリップにイベントを追加 |
| `unity_bake_animation` | ランタイムアニメーションをアニメーションクリップにベイク |
| `unity_import_mixamo_animation` | Mixamoからアニメーションをインポート/セットアップ |
| `unity_organize_animation_assets` | アニメーションファイルを適切なフォルダ構造に整理 |
| `unity_create_animation_layer_mask` | アニメーションレイヤー用アバターマスクを作成 |
| `unity_retarget_animation` | アニメーションを別のアバターにリターゲット |
| `unity_analyze_animation_performance` | アニメーションパフォーマンスを分析/最適化 |
| `unity_get_animation_details` | アニメーションクリップの詳細情報を取得 |
---
## Audio
| ツール | 説明 |
|--------|------|
| `unity_create_audio_mixer` | オーディオミキサーを作成 |
| `unity_create_audio_source` | AudioSourceコンポーネントを作成/構成 |
| `unity_setup_3d_audio` | 3D空間オーディオ設定を構成 |
| `unity_create_audio_clip` | オーディオファイルをインポートまたはプロシージャルオーディオクリップを作成 |
| `unity_setup_audio_effects` | オーディオエフェクトを追加/構成 |
| `unity_create_reverb_zones` | オーディオリバーブゾーンを作成 |
| `unity_setup_audio_occlusion` | オーディオオクルージョンを構成 |
| `unity_create_adaptive_music` | イントロ+ループミュージックシステムを作成 |
| `unity_setup_audio_triggers` | イベントベースオーディオトリガーを構成 |
| `unity_create_sound_pools` | サウンドバリエーションプールを作成 |
| `unity_create_audio_mixing` | リアルタイムオーディオミキシングとダッキングをセットアップ |
| `unity_setup_spatial_audio` | VR/AR用高度な空間オーディオを構成 |
| `unity_create_audio_visualization` | オーディオに反応するビジュアルエフェクトを作成 |
| `unity_get_audio_details` | オーディオクリップの詳細情報を取得 |
---
## Physics
| ツール | 説明 |
|--------|------|
| `unity_setup_physics` | GameObjectまたはグローバルの物理設定をセットアップ |
| `unity_setup_navmesh` | ナビゲーションメッシュをセットアップ |
| `unity_add_collider_extension` | 障害物回避用Collider拡張を追加 |
| `unity_get_physics_settings` | Unity物理設定を取得 |
| `unity_get_physics_info` | シーン内の物理オブジェクト情報を取得 |
| `unity_setup_ui_navigation` | UIナビゲーションシステムを作成 |
---
## UI
| ツール | 説明 |
|--------|------|
| `unity_create_ui` | UnityでUI要素を作成 |
| `unity_capture_ui_element` | 名前で特定のUI要素をキャプチャ |
| `unity_setup_ui_anchors` | アンカーとピボットを自動セットアップ |
| `unity_setup_ui_animation` | UIアニメーションをセットアップ(フェード、スケール、スライド) |
| `unity_create_ui_grid` | UIグリッドレイアウトを作成 |
| `unity_create_ui_notification` | 通知システムを作成 |
| `unity_create_ui_dialog` | モーダルダイアログを作成 |
| `unity_optimize_ui_canvas` | パフォーマンス用にCanvasを最適化 |
| `unity_apply_ui_theme` | UI要素に完全なテーマを適用 |
| `unity_set_ui_colors` | UI要素の色を設定 |
| `unity_style_ui_elements` | UI要素にスタイリングを適用 |
| `unity_add_ui_effects` | UI要素にビジュアルエフェクトを追加 |
| `unity_get_ui_info` | シーン内のUI要素情報を取得 |
| `unity_setup_ui_canvas` | 異なるレンダーモードでUI Canvasを構成 |
| `unity_set_ui_anchor` | UI要素のアンカープリセットを設定 |
---
## Scene
| ツール | 説明 |
|--------|------|
| `unity_manage_scene` | シーン管理操作 |
| `unity_load_scene` | エディタモードでシーンをロード |
| `unity_unload_scene` | エディタからシーンをアンロード |
| `unity_list_all_scenes` | プロジェクト内の全シーンファイルをリスト |
| `unity_add_scene_to_build` | Build Settingsにシーンを追加/削除 |
| `unity_get_scene_info` | 包括的なシーン情報を取得 |
| `unity_get_scene_summary` | 軽量なシーン概要を取得 |
| `unity_get_scene_changes_since` | タイムスタンプ以降のシーン変更を取得 |
| `unity_capture_scene_view` | Scene Viewのスクリーンショットをキャプチャ |
| `unity_pause_scene` | シーンビューを一時停止/再開 |
---
## Input
| ツール | 説明 |
|--------|------|
| `unity_setup_custom_input` | カスタム入力アクションとバインディングを構成 |
| `unity_create_gesture_recognition` | タッチまたはモーション用ジェスチャー認識をセットアップ |
| `unity_setup_haptic_feedback` | ハプティック/バイブレーションフィードバックを構成 |
| `unity_create_input_validation` | 入力検証システムを作成 |
| `unity_setup_accessibility_input` | 入力用アクセシビリティ機能を構成 |
| `unity_create_input_recording` | 入力記録/再生をセットアップ |
| `unity_setup_multitouch` | マルチタッチ入力処理を構成 |
| `unity_create_pinch_zoom` | ピンチトゥズーム機能をセットアップ |
| `unity_setup_swipe_detection` | スワイプジェスチャー検出を構成 |
| `unity_create_drag_drop` | ドラッグ&ドロップ機能をセットアップ |
| `unity_setup_touch_effects` | タッチ用ビジュアルエフェクトを構成 |
| `unity_get_input_settings` | Unity入力設定を取得 |
| `unity_setup_input_system` | テンプレート付きUnity Input Systemをセットアップ |
---
## Weather
| ツール | 説明 |
|--------|------|
| `unity_create_terrain` | Unityでテレインを作成 |
| `unity_modify_terrain` | テレインの高さまたはテクスチャを変更 |
| `unity_create_weather_system` | 完全な天候システムを作成 |
| `unity_set_weather_preset` | 異なる天候プリセットにトランジション |
| `unity_create_rain_effect` | リアルな雨エフェクトを作成 |
| `unity_create_snow_effect` | 降雪エフェクトを作成 |
| `unity_create_wind_effect` | オブジェクトに影響する風エフェクトを作成 |
| `unity_create_thunderstorm` | 完全な雷雨を作成 |
| `unity_get_terrain_info` | テレイン情報を取得 |
---
## TimeOfDay
| ツール | 説明 |
|--------|------|
| `unity_create_time_of_day` | 動的な昼夜サイクルを作成 |
| `unity_set_time_of_day` | 昼夜サイクルの特定の時間を設定 |
| `unity_create_day_night_preset` | プリセットライティング条件を適用 |
| `unity_create_time_event` | 特定の時間にトリガーされるイベントを作成 |
---
## GOAP AI
| ツール | 説明 |
|--------|------|
| `unity_setup_behavior_tree` | ビヘイビアツリーAIシステムをセットアップ |
| `unity_define_goap_goal` | GOAPエージェントのゴールを定義 |
| `unity_create_goap_action` | GOAPエージェントのアクションを作成 |
| `unity_generate_goap_action_set` | エージェントタイプに基づきアクションセットを自動生成 |
| `unity_setup_goap_world_state` | GOAPプランニング用ワールドステートを構成 |
| `unity_debug_goap_decisions` | GOAP判断を可視化/デバッグ |
| `unity_optimize_goap_performance` | GOAPエージェントパフォーマンスを最適化 |
| `unity_create_goap_agent` | GOAP AIエージェントを作成 |
| `unity_define_behavior_language` | 自然言語でAIビヘイビアを定義 |
| `unity_create_goap_template` | プロフェッショナルテンプレートからGOAP AIを作成 |
---
## AI
| ツール | 説明 |
|--------|------|
| `unity_setup_ml_agent` | 機械学習エージェントをセットアップ |
| `unity_create_neural_network` | ニューラルネットワークシステムを作成 |
| `unity_create_ai_pathfinding` | A*アルゴリズムでAIパスファインディングを作成 |
---
## GameSystems
| ツール | 説明 |
|--------|------|
| `unity_create_game_controller` | 異なるゲームタイプ用プレイヤーコントローラーを作成 |
| `unity_create_state_machine` | キャラクターまたはゲーム状態用ステートマシンを作成 |
| `unity_setup_inventory_system` | UI付きインベントリシステムを作成 |
| `unity_create_game_template` | 完全なゲームテンプレートを作成 |
| `unity_quick_prototype` | 素早くプレイ可能なプロトタイプを作成 |
---
## Scripting
| ツール | 説明 |
|--------|------|
| `unity_grep_scripts` | スクリプトファイル全体でパターンを検索 |
| `unity_read_script_range` | スクリプトの特定行範囲を読み取り |
| `unity_list_script_files` | globパターンサポート付きでスクリプトファイルをリスト |
| `unity_analyze_script` | 品質問題のための包括的なスクリプト分析 |
| `unity_modify_script` | 既存のUnityスクリプトを編集 |
| `unity_edit_script_line` | Unityスクリプトの特定行を編集 |
| `unity_add_script_method` | Unityスクリプトに新しいメソッドを追加 |
| `unity_update_script_variable` | 変数宣言または値を更新 |
| `unity_read_script` | Unityスクリプトファイルの内容を読み取り |
---
## Editor
| ツール | 説明 |
|--------|------|
| `unity_force_refresh_assets` | Unityにアセットデータベースの更新を強制 |
| `unity_invoke_context_menu` | コンポーネントの[ContextMenu]メソッドを呼び出し |
| `unity_execute_menu_item` | Unity Editorメニュー項目を実行 |
| `unity_get_inspector_info` | 詳細なインスペクター情報を取得 |
| `unity_get_selected_object_info` | 現在選択中のGameObject情報を取得 |
| `unity_get_component_details` | 特定コンポーネントの詳細を取得 |
| `unity_console` | Unityコンソールログ操作 |
| `unity_analyze_console_logs` | Unityコンソールログの詳細分析 |
---
## Screenshot
| ツール | 説明 |
|--------|------|
| `unity_capture_game_view` | Canvas/UI含むGame Viewをキャプチャ |
| `unity_capture_region` | 特定の領域をキャプチャ |
| `unity_get_screenshot_result` | スクリーンショットキャプチャ結果を取得 |
| `unity_capture_grid` | Game Viewをグリッドとして分割キャプチャ |
---
## Monitoring
| ツール | 説明 |
|--------|------|
| `unity_get_operation_history` | Unity操作履歴を取得 |
| `unity_create_checkpoint` | 後で復元するチェックポイントを作成 |
| `unity_restore_checkpoint` | 以前に作成したチェックポイントを復元 |
| `unity_monitor_play_state` | Unityプレイモード状態変更を監視 |
| `unity_monitor_file_changes` | プロジェクト内のファイル変更を監視 |
| `unity_monitor_compile` | スクリプトコンパイルイベントを監視 |
| `unity_subscribe_events` | Unityイベントを購読 |
| `unity_get_events` | 最近のUnityイベントを取得 |
| `unity_get_monitoring_status` | 現在の監視状態を取得 |
| `unity_monitor_runtime_errors` | リアルタイムランタイムエラー監視 |
---
## AssetManagement
| ツール | 説明 |
|--------|------|
| `unity_get_asset_dependencies` | アセットの全依存関係を取得 |
| `unity_check_folder` | フォルダの存在を確認 |
| `unity_create_folder` | 新しいフォルダを作成 |
| `unity_list_folders` | パス内のフォルダをリスト |
| `unity_cleanup_empty_objects` | シーンから空のGameObjectを削除 |
| `unity_rename_asset` | アセットファイルの名前を変更 |
| `unity_delete_asset` | プロジェクトからアセットを削除 |
| `unity_create_project_snapshot` | プロジェクト状態のスナップショットを作成 |
| `unity_analyze_dependencies` | アセット依存関係を分析/可視化 |
| `unity_export_project_structure` | プロジェクトフォルダ構造をエクスポート |
| `unity_validate_naming_conventions` | アセットが命名規則に従っているか確認 |
| `unity_auto_organize_folders` | アセットを自動的にフォルダに整理 |
---
## Optimization
| ツール | 説明 |
|--------|------|
| `unity_optimize_textures_batch` | テクスチャインポート設定を一括最適化 |
| `unity_analyze_draw_calls` | ドローコール最適化機会を分析 |
| `unity_estimate_build_size` | 異なるプラットフォーム用ビルドサイズを推定 |
| `unity_performance_report` | 包括的なパフォーマンスレポートを生成 |
| `unity_generate_lod` | LODグループを自動生成 |
| `unity_auto_atlas_textures` | テクスチャアトラスを自動作成 |
| `unity_check_best_practices` | ベストプラクティス準拠のためスクリプトを分析 |
---
## Batch
| ツール | 説明 |
|--------|------|
| `unity_extract_all_text` | ローカライゼーション用に全テキストコンテンツを抽出 |
| `unity_batch_rename` | 複数のアセットを一括リネーム |
| `unity_batch_prefab_update` | 複数のプレハブを変更で更新 |
| `unity_batch_material_apply` | 複数のオブジェクトにマテリアルを適用 |
| `unity_batch_prefab_create` | 複数のオブジェクトからプレハブを作成 |
---
## Build
| ツール | 説明 |
|--------|------|
| `unity_get_build_settings` | Unityビルド設定を取得 |
| `unity_get_player_settings` | Unityプレイヤー設定を取得 |
| `unity_get_quality_settings` | Unity品質設定を取得 |
| `unity_get_project_summary` | 全体的なプロジェクトサマリーを取得 |
---
## Package
| ツール | 説明 |
|--------|------|
| `unity_list_packages` | インストール済み全Unityパッケージをリスト |
| `unity_install_package` | Unityパッケージをインストール |
| `unity_check_package` | パッケージがインストール済みか確認 |
---
## Debug
| ツール | 説明 |
|--------|------|
| `unity_control_game_speed` | Unityゲーム速度(タイムスケール)を制御 |
| `unity_profile_performance` | Unityパフォーマンスプロファイリングデータを取得 |
| `unity_debug_draw` | Unityシーンにデバッグシェイプを描画 |
| `unity_run_tests` | Unity Test Runnerテストを実行 |
| `unity_manage_breakpoints` | デバッグブレークポイントを管理 |
| `unity_debug_bt` | ビヘイビアツリーのデバッグ情報を取得 |
---
## Timeline
| ツール | 説明 |
|--------|------|
| `unity_setup_avatar` | 3Dモデル用アバターを構成 |
| `unity_create_timeline` | シネマティクス用Unity Timelineを作成 |
---
## Utility
| ツール | 説明 |
|--------|------|
| `unity_add_target_to_group` | Cinemachine Target Groupにターゲットを追加 |
| `unity_create_script` | 新しいC#スクリプトを作成 |
| `unity_create_particle_system` | パーティクルシステムを作成 |
| `unity_create_particle_preset` | 高度なパーティクルエフェクトを作成 |
| `unity_place_objects` | パターンで複数のオブジェクトを配置 |
| `unity_undo_operation` | 最後のUnity操作を取り消し |
| `unity_redo_operation` | 以前に取り消した操作をやり直し |
| `unity_list_assets` | プロジェクト内のアセットをリスト |
| `unity_search_prefabs_by_component` | 特定コンポーネントを持つプレハブを検索 |
| `unity_find_material_usage` | マテリアルを使用している全オブジェクトを検索 |
| `unity_find_texture_usage` | テクスチャを使用している全マテリアルを検索 |
| `unity_find_missing_references` | プロジェクト内の欠落参照を検索 |
| `unity_group_gameobjects` | 複数のGameObjectを親の下にグループ化 |
| `unity_batch_import_settings` | 複数のアセットにインポート設定を適用 |
| `unity_find_unused_assets` | プロジェクトで参照されていないアセットを検索 |
| `unity_create_responsive_ui` | レスポンシブUIコンテナを作成 |
| `unity_setup_scroll_view` | 完全なスクロールビューを作成 |
| `unity_setup_safe_area` | モバイル用セーフエリアをセットアップ |
| `unity_get_asset_file_info` | アセットのファイルシステム情報を取得 |
| `unity_get_asset_import_settings` | アセットのインポート設定を取得 |
| `unity_search_code` | プロジェクト内でコードパターンを検索 |
| `unity_auto_attach_ui` | UI要素をコンポーネントフィールドに自動アタッチ |
| `unity_create_prefab` | GameObjectからプレハブを作成 |
| `unity_read_particle_system` | 全Particle Systemプロパティを読み取り |
| `unity_modify_particle_system` | Particle Systemプロパティを変更 |
---
## Other
以下を含む42の追加ツール:
- ビヘイビアツリー (`unity_create_bt_agent`, `unity_add_bt_node` など)
- ウォーターシステム (`unity_create_ocean_system`, `unity_add_buoyancy` など)
- HDRP Water (`unity_create_hdrp_water`, `unity_set_hdrp_water_property`)
- 特殊エフェクト (`unity_create_caustics`, `unity_trigger_dissolve` など)
- ランタイムステータス (`unity_get_runtime_status`, `unity_get_performance_metrics` など)
- 通信 (`unity_send_chat_response`, `unity_check_messages`)
---
## バージョン情報
- **ツール数**: 350
- **バージョン**: 1.2.0
- **最終更新**: 2026-01-19
---
*tool-registry.json から自動生成*
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: d149178da5978486899f8ff3c9f7e390
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Documentation/TOOLS_JP.txt
uploadId: 920982
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 10eaf66a492ee40429c4de4910e17171
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Documentation/TOOLS_JP.txt.zip
uploadId: 920982
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2b4ce59a57e94418292cfa5b84707b4c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,215 +0,0 @@
using UnityEditor;
using UnityEngine;
using SynapticAIPro;
using System.IO;
using System.Linq;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
namespace SynapticPro
{
/// <summary>
/// Detects installed Cinemachine version and defines appropriate scripting symbols
/// Supports both Cinemachine 2.x and 3.x
/// Automatically installs Unity.Splines dependency for Cinemachine 3.x
/// </summary>
[InitializeOnLoad]
public static class CinemachineVersionDetector
{
private const string CINEMACHINE_2_SYMBOL = "CINEMACHINE_2";
private const string CINEMACHINE_3_SYMBOL = "CINEMACHINE_3";
private const string CINEMACHINE_SYMBOL = "CINEMACHINE";
private const string SPLINES_PACKAGE = "com.unity.splines";
private static AddRequest splinesAddRequest;
static CinemachineVersionDetector()
{
DetectAndSetSymbols();
}
[MenuItem("Tools/Synaptic Pro/Detect Cinemachine Version")]
public static void DetectAndSetSymbols()
{
var cinemachineVersion = GetCinemachineVersion();
if (cinemachineVersion == null)
{
SynLog.Info("[Synaptic] Cinemachine not detected. Cinemachine features will be disabled.");
RemoveAllCinemachineSymbols();
return;
}
SynLog.Info($"[Synaptic] Detected Cinemachine version: {cinemachineVersion}");
// Parse version
var versionParts = cinemachineVersion.Split('.');
if (versionParts.Length > 0 && int.TryParse(versionParts[0], out int majorVersion))
{
if (majorVersion >= 3)
{
SetCinemachineSymbol(3);
SynLog.Info("[Synaptic] ✅ Cinemachine 3.x detected - Using Cinemachine 3 API");
// Cinemachine 3.x requires Unity.Splines package
CheckAndInstallSplines();
}
else if (majorVersion == 2)
{
SetCinemachineSymbol(2);
SynLog.Info("[Synaptic] ✅ Cinemachine 2.x detected - Using Cinemachine 2 API");
}
else
{
SynLog.Warn($"[Synaptic] ⚠️ Unsupported Cinemachine version: {cinemachineVersion}. Recommended: 2.9.7 or 3.0+");
RemoveAllCinemachineSymbols();
}
}
}
private static string GetCinemachineVersion()
{
// Check via PackageInfo
var request = UnityEditor.PackageManager.Client.List(true, false);
// Wait for completion (synchronous for InitializeOnLoad)
while (!request.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (request.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var cinemachinePackage = request.Result.FirstOrDefault(p => p.name == "com.unity.cinemachine");
if (cinemachinePackage != null)
{
return cinemachinePackage.version;
}
}
// Fallback: Check if namespace exists via type checking
var cinemachine2Type = System.Type.GetType("Cinemachine.CinemachineVirtualCamera, Cinemachine");
var cinemachine3Type = System.Type.GetType("Unity.Cinemachine.CinemachineCamera, Unity.Cinemachine");
if (cinemachine3Type != null)
{
return "3.0.0"; // 3.x detected
}
else if (cinemachine2Type != null)
{
return "2.9.7"; // 2.x detected
}
return null; // Not installed
}
private static void SetCinemachineSymbol(int majorVersion)
{
var buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
var definesList = defines.Split(';').ToList();
// Remove old symbols
definesList.Remove(CINEMACHINE_2_SYMBOL);
definesList.Remove(CINEMACHINE_3_SYMBOL);
definesList.Remove(CINEMACHINE_SYMBOL);
// Add appropriate symbols
definesList.Add(CINEMACHINE_SYMBOL);
if (majorVersion == 2)
{
definesList.Add(CINEMACHINE_2_SYMBOL);
}
else if (majorVersion == 3)
{
definesList.Add(CINEMACHINE_3_SYMBOL);
}
var newDefines = string.Join(";", definesList.Distinct().Where(s => !string.IsNullOrEmpty(s)));
PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, newDefines);
SynLog.Info($"[Synaptic] Scripting symbols updated: {newDefines}");
}
private static void RemoveAllCinemachineSymbols()
{
var buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup);
var definesList = defines.Split(';').ToList();
definesList.Remove(CINEMACHINE_2_SYMBOL);
definesList.Remove(CINEMACHINE_3_SYMBOL);
definesList.Remove(CINEMACHINE_SYMBOL);
var newDefines = string.Join(";", definesList.Distinct().Where(s => !string.IsNullOrEmpty(s)));
PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, newDefines);
SynLog.Info("[Synaptic] Cinemachine symbols removed");
}
private static void CheckAndInstallSplines()
{
var listRequest = Client.List(true, false);
// Wait for completion (synchronous for simplicity)
while (!listRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (listRequest.Status == StatusCode.Success)
{
bool isInstalled = false;
foreach (var package in listRequest.Result)
{
if (package.name == SPLINES_PACKAGE)
{
isInstalled = true;
SynLog.Info($"[Synaptic] Unity.Splines is already installed (version {package.version})");
break;
}
}
if (!isInstalled)
{
SynLog.Info("[Synaptic] Unity.Splines not found. Installing dependency for Cinemachine 3.x...");
InstallSplines();
}
}
else if (listRequest.Status >= StatusCode.Failure)
{
Debug.LogError($"[Synaptic] Failed to check Splines package: {listRequest.Error.message}");
}
}
private static void InstallSplines()
{
splinesAddRequest = Client.Add(SPLINES_PACKAGE);
EditorApplication.update += CheckSplinesInstallProgress;
}
private static void CheckSplinesInstallProgress()
{
if (splinesAddRequest == null || !splinesAddRequest.IsCompleted)
return;
EditorApplication.update -= CheckSplinesInstallProgress;
if (splinesAddRequest.Status == StatusCode.Success)
{
SynLog.Info($"[Synaptic] ✅ Successfully installed {SPLINES_PACKAGE} for Cinemachine 3.x");
SynLog.Info("[Synaptic] Please wait for Unity to recompile scripts...");
}
else if (splinesAddRequest.Status >= StatusCode.Failure)
{
Debug.LogError($"[Synaptic] ❌ Failed to install {SPLINES_PACKAGE}: {splinesAddRequest.Error.message}");
SynLog.Warn("[Synaptic] Cinemachine 3.x requires Unity.Splines package.\n" +
"Please install it manually via Package Manager:\n" +
"Window > Package Manager > + > Add package by name...\n" +
$"Package name: {SPLINES_PACKAGE}");
}
splinesAddRequest = null;
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 5af59eb77df3e4503957595c272c14e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/CinemachineVersionDetector.cs
uploadId: 920982
@@ -1,85 +0,0 @@
using UnityEngine;
using SynapticAIPro;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
namespace SynapticPro
{
[InitializeOnLoad]
public static class NewtonsoftInstaller
{
private const string NEWTONSOFT_PACKAGE = "com.unity.nuget.newtonsoft-json";
private static AddRequest addRequest;
private static ListRequest listRequest;
static NewtonsoftInstaller()
{
// Check if Newtonsoft.Json is already installed
listRequest = Client.List();
EditorApplication.update += CheckListProgress;
}
private static void CheckListProgress()
{
if (listRequest == null || !listRequest.IsCompleted)
return;
EditorApplication.update -= CheckListProgress;
if (listRequest.Status == StatusCode.Success)
{
bool isInstalled = false;
foreach (var package in listRequest.Result)
{
if (package.name == NEWTONSOFT_PACKAGE)
{
isInstalled = true;
SynLog.Info($"[Synaptic AI Pro] Newtonsoft.Json is already installed (version {package.version})");
break;
}
}
if (!isInstalled)
{
SynLog.Info("[Synaptic AI Pro] Newtonsoft.Json not found. Installing...");
InstallNewtonsoftJson();
}
}
else if (listRequest.Status >= StatusCode.Failure)
{
Debug.LogError($"[Synaptic AI Pro] Failed to list packages: {listRequest.Error.message}");
}
listRequest = null;
}
private static void InstallNewtonsoftJson()
{
addRequest = Client.Add(NEWTONSOFT_PACKAGE);
EditorApplication.update += CheckInstallProgress;
}
private static void CheckInstallProgress()
{
if (addRequest == null || !addRequest.IsCompleted)
return;
EditorApplication.update -= CheckInstallProgress;
if (addRequest.Status == StatusCode.Success)
{
SynLog.Info($"[Synaptic AI Pro] Successfully installed {NEWTONSOFT_PACKAGE}");
}
else if (addRequest.Status >= StatusCode.Failure)
{
Debug.LogError($"[Synaptic AI Pro] Failed to install {NEWTONSOFT_PACKAGE}: {addRequest.Error.message}");
SynLog.Warn("[Synaptic AI Pro] Please install Newtonsoft.Json manually via Package Manager:\n" +
"Window > Package Manager > + > Add package by name...\n" +
"Package name: com.unity.nuget.newtonsoft-json");
}
addRequest = null;
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 94dec97004834400dbc51f312324888c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NewtonsoftInstaller.cs
uploadId: 920982
@@ -1,54 +0,0 @@
{
"name": "Synaptic.MCP.Unity.Editor",
"rootNamespace": "SynapticPro",
"references": [
"Synaptic.MCP.Unity",
"Unity.Mathematics",
"Unity.Collections",
"Unity.Burst",
"Unity.TextMeshPro",
"Unity.Nuget.Newtonsoft-Json",
"Unity.ugui",
"Cinemachine",
"Unity.Cinemachine",
"Unity.Splines",
"Unity.VisualEffectGraph.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.cinemachine",
"expression": "[3.0,4.0)",
"define": "CINEMACHINE_3"
},
{
"name": "com.unity.cinemachine",
"expression": "[2.0,3.0)",
"define": "CINEMACHINE_2"
},
{
"name": "com.unity.cinemachine",
"expression": "",
"define": "CINEMACHINE"
},
{
"name": "com.unity.visualeffectgraph",
"expression": "",
"define": "VFX_GRAPH_PACKAGE"
},
{
"name": "com.unity.visualeffectgraph",
"expression": "[10.0,)",
"define": "VFX_GRAPH_10_PLUS"
}
],
"noEngineReferences": false
}
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 343c464e684b044469a954b748d0e625
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusAI.MCP.Unity.Editor.asmdef
uploadId: 920982
@@ -1,801 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using SynapticAIPro;
namespace SynapticPro
{
/// <summary>
/// Helper class for advanced animation and motion management
/// Handles Mixamo integration, IK setup, and animation asset management
/// </summary>
public static class NexusAnimationHelper
{
private const string MIXAMO_RIG_PREFIX = "mixamorig:";
private const string HUMANOID_AVATAR_PREFIX = "Avatar_";
/// <summary>
/// Import and setup Mixamo FBX with automatic Humanoid configuration
/// </summary>
public static string ImportMixamoAnimation(Dictionary<string, string> parameters)
{
try
{
string fbxPath = parameters.GetValueOrDefault("fbxPath", "");
string targetPath = parameters.GetValueOrDefault("targetPath", "Assets/Animations/Mixamo/");
bool createController = parameters.GetValueOrDefault("createController", "true") == "true";
string characterName = parameters.GetValueOrDefault("characterName", "Character");
bool setupIK = parameters.GetValueOrDefault("setupIK", "true") == "true";
if (string.IsNullOrEmpty(fbxPath) || !File.Exists(fbxPath))
{
return $"Error: FBX file not found at path: {fbxPath}";
}
// Ensure target directory exists
if (!AssetDatabase.IsValidFolder(targetPath))
{
string[] folders = targetPath.Split('/');
string currentPath = folders[0];
for (int i = 1; i < folders.Length; i++)
{
if (string.IsNullOrEmpty(folders[i])) continue;
string nextPath = currentPath + "/" + folders[i];
if (!AssetDatabase.IsValidFolder(nextPath))
{
AssetDatabase.CreateFolder(currentPath, folders[i]);
}
currentPath = nextPath;
}
}
// Copy FBX to project
string fileName = Path.GetFileName(fbxPath);
string assetPath = Path.Combine(targetPath, fileName);
File.Copy(fbxPath, assetPath, true);
AssetDatabase.Refresh();
// Configure as Humanoid
ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
if (importer != null)
{
// Setup for Mixamo
importer.animationType = ModelImporterAnimationType.Human;
importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
// Animation settings
importer.importAnimation = true;
importer.animationCompression = ModelImporterAnimationCompression.Optimal;
// Optimize for Mixamo
importer.optimizeGameObjects = true;
importer.optimizeMeshPolygons = true;
importer.optimizeMeshVertices = true;
// Material settings
importer.materialImportMode = ModelImporterMaterialImportMode.ImportStandard;
// Apply settings
importer.SaveAndReimport();
var result = new Dictionary<string, object>
{
["success"] = true,
["assetPath"] = assetPath,
["characterName"] = characterName
};
// Create Animator Controller if requested
if (createController)
{
string controllerPath = CreateAnimatorControllerForMixamo(assetPath, characterName, targetPath);
result["controllerPath"] = controllerPath;
}
// Setup IK if requested
if (setupIK)
{
SetupBasicIK(characterName);
result["ikSetup"] = true;
}
return JsonUtility.ToJson(result);
}
return "Error: Failed to configure model importer";
}
catch (Exception e)
{
return $"Error importing Mixamo animation: {e.Message}";
}
}
/// <summary>
/// Organize and categorize animation clips
/// </summary>
public static string OrganizeAnimationAssets(Dictionary<string, string> parameters)
{
try
{
string sourcePath = parameters.GetValueOrDefault("sourcePath", "Assets/Animations/");
bool autoDetectType = parameters.GetValueOrDefault("autoDetectType", "true") == "true";
bool createFolders = parameters.GetValueOrDefault("createFolders", "true") == "true";
// Find all animation clips
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { sourcePath });
var categories = new Dictionary<string, List<AnimationClip>>
{
["Idle"] = new List<AnimationClip>(),
["Walk"] = new List<AnimationClip>(),
["Run"] = new List<AnimationClip>(),
["Jump"] = new List<AnimationClip>(),
["Attack"] = new List<AnimationClip>(),
["Death"] = new List<AnimationClip>(),
["Damage"] = new List<AnimationClip>(),
["Other"] = new List<AnimationClip>()
};
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
if (clip != null && autoDetectType)
{
string category = DetectAnimationType(clip.name);
categories[category].Add(clip);
// Move to categorized folder if requested
if (createFolders)
{
string targetFolder = Path.Combine(sourcePath, category);
if (!AssetDatabase.IsValidFolder(targetFolder))
{
AssetDatabase.CreateFolder(sourcePath, category);
}
string newPath = Path.Combine(targetFolder, Path.GetFileName(path));
if (path != newPath)
{
AssetDatabase.MoveAsset(path, newPath);
}
}
}
}
// Generate report
var report = new Dictionary<string, object>
{
["totalClips"] = guids.Length,
["categories"] = new Dictionary<string, int>()
};
foreach (var category in categories)
{
((Dictionary<string, int>)report["categories"])[category.Key] = category.Value.Count;
}
return JsonUtility.ToJson(report);
}
catch (Exception e)
{
return $"Error organizing animation assets: {e.Message}";
}
}
/// <summary>
/// Setup IK for character
/// </summary>
public static string SetupCharacterIK(Dictionary<string, string> parameters)
{
try
{
string gameObjectName = parameters.GetValueOrDefault("gameObject", "");
bool enableFootIK = parameters.GetValueOrDefault("enableFootIK", "true") == "true";
bool enableHandIK = parameters.GetValueOrDefault("enableHandIK", "false") == "true";
bool enableLookAt = parameters.GetValueOrDefault("enableLookAt", "false") == "true";
float footIKWeight = float.Parse(parameters.GetValueOrDefault("footIKWeight", "1"));
float handIKWeight = float.Parse(parameters.GetValueOrDefault("handIKWeight", "1"));
GameObject target = GameObject.Find(gameObjectName);
if (target == null)
{
return $"Error: GameObject '{gameObjectName}' not found";
}
// Add IK controller component if not exists
var ikController = target.GetComponent<IKController>();
if (ikController == null)
{
ikController = target.AddComponent<IKController>();
Undo.RegisterCreatedObjectUndo(ikController, "Add IK Controller");
}
// Configure IK settings
ikController.enableFootIK = enableFootIK;
ikController.enableHandIK = enableHandIK;
ikController.enableLookAt = enableLookAt;
ikController.footIKWeight = footIKWeight;
ikController.handIKWeight = handIKWeight;
// Setup IK targets
if (enableFootIK)
{
CreateIKTarget(target.transform, "LeftFootIK", new Vector3(-0.1f, 0, 0));
CreateIKTarget(target.transform, "RightFootIK", new Vector3(0.1f, 0, 0));
}
if (enableHandIK)
{
CreateIKTarget(target.transform, "LeftHandIK", new Vector3(-0.5f, 1.5f, 0.5f));
CreateIKTarget(target.transform, "RightHandIK", new Vector3(0.5f, 1.5f, 0.5f));
}
if (enableLookAt)
{
CreateIKTarget(target.transform, "LookAtTarget", new Vector3(0, 1.6f, 2f));
}
EditorUtility.SetDirty(target);
return JsonUtility.ToJson(new Dictionary<string, object>
{
["success"] = true,
["gameObject"] = gameObjectName,
["footIK"] = enableFootIK,
["handIK"] = enableHandIK,
["lookAt"] = enableLookAt
});
}
catch (Exception e)
{
return $"Error setting up IK: {e.Message}";
}
}
/// <summary>
/// Create animation layer mask
/// </summary>
public static string CreateAnimationLayerMask(Dictionary<string, string> parameters)
{
try
{
string maskName = parameters.GetValueOrDefault("maskName", "NewLayerMask");
string savePath = parameters.GetValueOrDefault("savePath", "Assets/Animations/Masks/");
string includeBonesPattern = parameters.GetValueOrDefault("includeBones", "");
string excludeBonesPattern = parameters.GetValueOrDefault("excludeBones", "");
string avatarPath = parameters.GetValueOrDefault("avatarPath", "");
if (!AssetDatabase.IsValidFolder(savePath))
{
Directory.CreateDirectory(savePath);
AssetDatabase.Refresh();
}
// Create avatar mask
AvatarMask mask = new AvatarMask();
mask.name = maskName;
// Load avatar if specified
if (!string.IsNullOrEmpty(avatarPath))
{
Avatar avatar = AssetDatabase.LoadAssetAtPath<Avatar>(avatarPath);
if (avatar != null)
{
// Configure body parts based on patterns
ConfigureAvatarMaskBodyParts(mask, includeBonesPattern, excludeBonesPattern);
}
}
string assetPath = Path.Combine(savePath, maskName + ".mask");
AssetDatabase.CreateAsset(mask, assetPath);
AssetDatabase.SaveAssets();
return JsonUtility.ToJson(new Dictionary<string, object>
{
["success"] = true,
["maskPath"] = assetPath,
["maskName"] = maskName
});
}
catch (Exception e)
{
return $"Error creating layer mask: {e.Message}";
}
}
/// <summary>
/// Setup animation blend tree
/// </summary>
public static string SetupAdvancedBlendTree(Dictionary<string, string> parameters)
{
try
{
string controllerPath = parameters.GetValueOrDefault("controllerPath", "");
string blendType = parameters.GetValueOrDefault("blendType", "2D");
string parameterX = parameters.GetValueOrDefault("parameterX", "MoveX");
string parameterY = parameters.GetValueOrDefault("parameterY", "MoveY");
string clips = parameters.GetValueOrDefault("clips", ""); // Comma separated paths
var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
if (controller == null)
{
return "Error: Animator Controller not found";
}
// Create blend tree
var rootStateMachine = controller.layers[0].stateMachine;
var blendTreeState = rootStateMachine.AddState("BlendTree");
BlendTree blendTree;
controller.CreateBlendTreeInController("Movement", out blendTree);
blendTreeState.motion = blendTree;
// Configure blend type
switch (blendType.ToLower())
{
case "1d":
blendTree.blendType = BlendTreeType.Simple1D;
blendTree.blendParameter = parameterX;
break;
case "2d":
blendTree.blendType = BlendTreeType.SimpleDirectional2D;
blendTree.blendParameter = parameterX;
blendTree.blendParameterY = parameterY;
break;
case "freeform":
blendTree.blendType = BlendTreeType.FreeformDirectional2D;
blendTree.blendParameter = parameterX;
blendTree.blendParameterY = parameterY;
break;
}
// Add animation clips
if (!string.IsNullOrEmpty(clips))
{
string[] clipPaths = clips.Split(',');
foreach (string clipPath in clipPaths)
{
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(clipPath.Trim());
if (clip != null)
{
blendTree.AddChild(clip);
}
}
}
// Add parameters if not exist
if (!controller.parameters.Any(p => p.name == parameterX))
{
controller.AddParameter(parameterX, AnimatorControllerParameterType.Float);
}
if (!string.IsNullOrEmpty(parameterY) && !controller.parameters.Any(p => p.name == parameterY))
{
controller.AddParameter(parameterY, AnimatorControllerParameterType.Float);
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(controller), ImportAssetOptions.ForceUpdate);
return JsonUtility.ToJson(new Dictionary<string, object>
{
["success"] = true,
["blendTreeName"] = "Movement",
["blendType"] = blendType,
["clipCount"] = blendTree.children.Length
});
}
catch (Exception e)
{
return $"Error setting up blend tree: {e.Message}";
}
}
/// <summary>
/// Retarget animation from one rig to another
/// </summary>
public static string RetargetAnimation(Dictionary<string, string> parameters)
{
try
{
string sourceClipPath = parameters.GetValueOrDefault("sourceClip", "");
string targetAvatarPath = parameters.GetValueOrDefault("targetAvatar", "");
string outputPath = parameters.GetValueOrDefault("outputPath", "Assets/Animations/Retargeted/");
bool adjustRootMotion = parameters.GetValueOrDefault("adjustRootMotion", "true") == "true";
AnimationClip sourceClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(sourceClipPath);
Avatar targetAvatar = AssetDatabase.LoadAssetAtPath<Avatar>(targetAvatarPath);
if (sourceClip == null || targetAvatar == null)
{
return "Error: Source clip or target avatar not found";
}
// Create output directory
if (!AssetDatabase.IsValidFolder(outputPath))
{
Directory.CreateDirectory(outputPath);
AssetDatabase.Refresh();
}
// Clone animation clip
AnimationClip retargetedClip = UnityEngine.Object.Instantiate(sourceClip);
retargetedClip.name = sourceClip.name + "_Retargeted";
// Adjust root motion if needed
if (adjustRootMotion)
{
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(retargetedClip);
settings.loopTime = sourceClip.isLooping;
settings.keepOriginalPositionY = true;
settings.keepOriginalOrientation = true;
AnimationUtility.SetAnimationClipSettings(retargetedClip, settings);
}
string savePath = Path.Combine(outputPath, retargetedClip.name + ".anim");
AssetDatabase.CreateAsset(retargetedClip, savePath);
AssetDatabase.SaveAssets();
return JsonUtility.ToJson(new Dictionary<string, object>
{
["success"] = true,
["retargetedClip"] = savePath,
["originalClip"] = sourceClipPath
});
}
catch (Exception e)
{
return $"Error retargeting animation: {e.Message}";
}
}
/// <summary>
/// Create animation transition presets
/// </summary>
public static string CreateTransitionPreset(Dictionary<string, string> parameters)
{
try
{
string controllerPath = parameters.GetValueOrDefault("controllerPath", "");
string fromState = parameters.GetValueOrDefault("fromState", "");
string toState = parameters.GetValueOrDefault("toState", "");
string presetType = parameters.GetValueOrDefault("presetType", "smooth"); // smooth, instant, crossfade
float duration = float.Parse(parameters.GetValueOrDefault("duration", "0.25"));
var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
if (controller == null)
{
return "Error: Controller not found";
}
var stateMachine = controller.layers[0].stateMachine;
var sourceState = FindState(stateMachine, fromState);
var destState = FindState(stateMachine, toState);
if (sourceState == null || destState == null)
{
return "Error: States not found";
}
var transition = sourceState.AddTransition(destState);
// Configure based on preset
switch (presetType.ToLower())
{
case "instant":
transition.duration = 0;
transition.offset = 0;
transition.hasExitTime = false;
break;
case "smooth":
transition.duration = duration;
transition.offset = 0;
transition.hasExitTime = true;
transition.exitTime = 0.75f;
break;
case "crossfade":
transition.duration = duration * 2;
transition.offset = 0.1f;
transition.hasExitTime = true;
transition.exitTime = 0.5f;
break;
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(controller), ImportAssetOptions.ForceUpdate);
return JsonUtility.ToJson(new Dictionary<string, object>
{
["success"] = true,
["fromState"] = fromState,
["toState"] = toState,
["presetType"] = presetType
});
}
catch (Exception e)
{
return $"Error creating transition preset: {e.Message}";
}
}
/// <summary>
/// Analyze animation performance
/// </summary>
public static string AnalyzeAnimationPerformance(Dictionary<string, string> parameters)
{
try
{
string targetPath = parameters.GetValueOrDefault("targetPath", "Assets/Animations/");
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { targetPath });
var report = new Dictionary<string, object>
{
["totalClips"] = guids.Length,
["clips"] = new List<Dictionary<string, object>>()
};
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
if (clip != null)
{
var clipInfo = new Dictionary<string, object>
{
["name"] = clip.name,
["length"] = clip.length,
["frameRate"] = clip.frameRate,
["isHumanMotion"] = clip.humanMotion,
["isLooping"] = clip.isLooping,
["events"] = clip.events.Length,
["approximateSize"] = EstimateAnimationSize(clip)
};
((List<Dictionary<string, object>>)report["clips"]).Add(clipInfo);
}
}
return JsonUtility.ToJson(report);
}
catch (Exception e)
{
return $"Error analyzing animation performance: {e.Message}";
}
}
// ===== Helper Methods =====
private static string CreateAnimatorControllerForMixamo(string fbxPath, string characterName, string targetPath)
{
string controllerPath = Path.Combine(targetPath, characterName + "_Controller.controller");
var controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
// Create parameters
controller.AddParameter("Speed", AnimatorControllerParameterType.Float);
controller.AddParameter("Direction", AnimatorControllerParameterType.Float);
controller.AddParameter("IsGrounded", AnimatorControllerParameterType.Bool);
controller.AddParameter("Jump", AnimatorControllerParameterType.Trigger);
// Create default states
var stateMachine = controller.layers[0].stateMachine;
var idleState = stateMachine.AddState("Idle");
stateMachine.defaultState = idleState;
// Try to find and assign idle animation from the FBX
var clips = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<AnimationClip>().ToArray();
foreach (var clip in clips)
{
if (clip.name.ToLower().Contains("idle"))
{
idleState.motion = clip;
break;
}
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate);
return controllerPath;
}
private static void SetupBasicIK(string characterName)
{
// This would normally set up IK components
// Actual implementation would depend on the IK solution being used
SynLog.Info($"IK setup prepared for {characterName}");
}
private static string DetectAnimationType(string clipName)
{
string lowerName = clipName.ToLower();
if (lowerName.Contains("idle") || lowerName.Contains("stand"))
return "Idle";
if (lowerName.Contains("walk"))
return "Walk";
if (lowerName.Contains("run") || lowerName.Contains("sprint"))
return "Run";
if (lowerName.Contains("jump") || lowerName.Contains("leap"))
return "Jump";
if (lowerName.Contains("attack") || lowerName.Contains("punch") || lowerName.Contains("kick"))
return "Attack";
if (lowerName.Contains("death") || lowerName.Contains("die"))
return "Death";
if (lowerName.Contains("damage") || lowerName.Contains("hit") || lowerName.Contains("hurt"))
return "Damage";
return "Other";
}
private static GameObject CreateIKTarget(Transform parent, string name, Vector3 localPosition)
{
GameObject ikTarget = new GameObject(name);
ikTarget.transform.SetParent(parent);
ikTarget.transform.localPosition = localPosition;
// Add visual gizmo component for editor
var gizmo = ikTarget.AddComponent<IKTargetGizmo>();
gizmo.color = name.Contains("Foot") ? Color.green : (name.Contains("Hand") ? Color.blue : Color.yellow);
Undo.RegisterCreatedObjectUndo(ikTarget, $"Create {name}");
return ikTarget;
}
private static void ConfigureAvatarMaskBodyParts(AvatarMask mask, string includeBones, string excludeBones)
{
// Configure humanoid body parts
for (int i = 0; i < (int)AvatarMaskBodyPart.LastBodyPart; i++)
{
bool include = true;
AvatarMaskBodyPart part = (AvatarMaskBodyPart)i;
string partName = part.ToString().ToLower();
if (!string.IsNullOrEmpty(excludeBones) && excludeBones.ToLower().Contains(partName))
{
include = false;
}
if (!string.IsNullOrEmpty(includeBones) && !includeBones.ToLower().Contains(partName))
{
include = false;
}
mask.SetHumanoidBodyPartActive(part, include);
}
}
private static AnimatorState FindState(AnimatorStateMachine stateMachine, string name)
{
foreach (var state in stateMachine.states)
{
if (state.state.name == name)
return state.state;
}
return null;
}
private static float EstimateAnimationSize(AnimationClip clip)
{
// Rough estimation based on curves and keys
var bindings = AnimationUtility.GetCurveBindings(clip);
int totalKeys = 0;
foreach (var binding in bindings)
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve != null)
{
totalKeys += curve.keys.Length;
}
}
// Approximate size in KB (4 bytes per float * keys * 4 values per key)
return (totalKeys * 4 * 4) / 1024f;
}
}
/// <summary>
/// IK Controller component for runtime IK handling
/// </summary>
public class IKController : MonoBehaviour
{
public bool enableFootIK = true;
public bool enableHandIK = false;
public bool enableLookAt = false;
public float footIKWeight = 1f;
public float handIKWeight = 1f;
public float lookAtWeight = 1f;
public Transform leftFootTarget;
public Transform rightFootTarget;
public Transform leftHandTarget;
public Transform rightHandTarget;
public Transform lookAtTarget;
private Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
void OnAnimatorIK(int layerIndex)
{
if (animator == null) return;
// Foot IK
if (enableFootIK)
{
if (leftFootTarget != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, footIKWeight);
animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, footIKWeight);
animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootTarget.position);
animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootTarget.rotation);
}
if (rightFootTarget != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, footIKWeight);
animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, footIKWeight);
animator.SetIKPosition(AvatarIKGoal.RightFoot, rightFootTarget.position);
animator.SetIKRotation(AvatarIKGoal.RightFoot, rightFootTarget.rotation);
}
}
// Hand IK
if (enableHandIK)
{
if (leftHandTarget != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, handIKWeight);
animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, handIKWeight);
animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandTarget.position);
animator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandTarget.rotation);
}
if (rightHandTarget != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, handIKWeight);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand, handIKWeight);
animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position);
animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandTarget.rotation);
}
}
// Look At
if (enableLookAt && lookAtTarget != null)
{
animator.SetLookAtWeight(lookAtWeight);
animator.SetLookAtPosition(lookAtTarget.position);
}
}
}
/// <summary>
/// Visual gizmo for IK targets
/// </summary>
public class IKTargetGizmo : MonoBehaviour
{
public Color color = Color.green;
public float size = 0.1f;
void OnDrawGizmos()
{
Gizmos.color = color;
Gizmos.DrawWireSphere(transform.position, size);
Gizmos.DrawLine(transform.position, transform.position + transform.forward * size * 2);
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 12ea05feed0164e43b479cec9d83a119
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusAnimationHelper.cs
uploadId: 920982
@@ -1,728 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Text;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Detailed asset information analysis class
/// Retrieves detailed information for textures, meshes, audio, animations, etc.
/// </summary>
public static class NexusAssetAnalyzer
{
/// <summary>
/// Get detailed information for texture assets
/// </summary>
public static string GetTextureDetails(Dictionary<string, string> parameters)
{
try
{
var textureName = parameters.GetValueOrDefault("textureName", "");
var includeAll = parameters.GetValueOrDefault("includeAll", "false") == "true";
var includeMipmaps = parameters.GetValueOrDefault("includeMipmaps", "true") == "true";
var includeCompressionInfo = parameters.GetValueOrDefault("includeCompressionInfo", "true") == "true";
var includeMemoryUsage = parameters.GetValueOrDefault("includeMemoryUsage", "true") == "true";
var textures = string.IsNullOrEmpty(textureName) && includeAll ?
Resources.FindObjectsOfTypeAll<Texture2D>() :
Resources.FindObjectsOfTypeAll<Texture2D>().Where(t =>
string.IsNullOrEmpty(textureName) || t.name.Contains(textureName)).ToArray();
var textureDetails = new Dictionary<string, object>
{
["total_count"] = textures.Length,
["search_criteria"] = new Dictionary<string, object>
{
["texture_name"] = textureName,
["include_all"] = includeAll,
["include_mipmaps"] = includeMipmaps,
["include_compression_info"] = includeCompressionInfo,
["include_memory_usage"] = includeMemoryUsage
},
["textures"] = textures.Take(20).Select(texture => {
if (texture == null) return null;
var textureData = new Dictionary<string, object>
{
["name"] = texture.name,
["size"] = new Dictionary<string, int>
{
["width"] = texture.width,
["height"] = texture.height
},
["format"] = texture.format.ToString(),
["filter_mode"] = texture.filterMode.ToString(),
["wrap_mode"] = texture.wrapMode.ToString(),
["is_readable"] = texture.isReadable
};
if (includeMipmaps)
{
textureData["mipmap_info"] = new Dictionary<string, object>
{
["mipmap_count"] = texture.mipmapCount,
["has_mipmaps"] = texture.mipmapCount > 1
};
}
if (includeMemoryUsage)
{
var memorySize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(texture);
textureData["memory_usage"] = new Dictionary<string, object>
{
["bytes"] = memorySize,
["formatted"] = FormatBytes(memorySize)
};
}
if (includeCompressionInfo)
{
var assetPath = AssetDatabase.GetAssetPath(texture);
if (!string.IsNullOrEmpty(assetPath))
{
var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (importer != null)
{
textureData["import_settings"] = new Dictionary<string, object>
{
["asset_path"] = assetPath,
["texture_type"] = importer.textureType.ToString(),
["max_texture_size"] = importer.maxTextureSize,
["compression"] = importer.textureCompression.ToString(),
["srgb"] = importer.sRGBTexture
};
}
}
}
return textureData;
}).Where(t => t != null).ToList()
};
return JsonConvert.SerializeObject(textureDetails, Formatting.Indented);
}
catch (Exception e)
{
return $"Error analyzing textures: {e.Message}";
}
}
/// <summary>
/// Get detailed information for mesh assets
/// </summary>
public static string GetMeshDetails(Dictionary<string, string> parameters)
{
try
{
var meshName = parameters.GetValueOrDefault("meshName", "");
var includeAll = parameters.GetValueOrDefault("includeAll", "false") == "true";
var includeVertexData = parameters.GetValueOrDefault("includeVertexData", "true") == "true";
var includeSubMeshes = parameters.GetValueOrDefault("includeSubMeshes", "true") == "true";
var includeBoneWeights = parameters.GetValueOrDefault("includeBoneWeights", "true") == "true";
var includeBlendShapes = parameters.GetValueOrDefault("includeBlendShapes", "false") == "true";
var meshes = string.IsNullOrEmpty(meshName) && includeAll ?
Resources.FindObjectsOfTypeAll<Mesh>() :
Resources.FindObjectsOfTypeAll<Mesh>().Where(m =>
string.IsNullOrEmpty(meshName) || m.name.Contains(meshName)).ToArray();
var report = new StringBuilder();
report.AppendLine("=== Mesh Details ===");
report.AppendLine($"Found {meshes.Length} mesh(es)");
report.AppendLine();
foreach (var mesh in meshes.Take(20))
{
if (mesh == null) continue;
report.AppendLine($"Mesh: {mesh.name}");
if (includeVertexData)
{
report.AppendLine($" Vertex Count: {mesh.vertexCount:N0}");
report.AppendLine($" Triangle Count: {mesh.triangles.Length / 3:N0}");
report.AppendLine($" UV Channels: {GetUVChannelCount(mesh)}");
report.AppendLine($" Has Normals: {mesh.normals.Length > 0}");
report.AppendLine($" Has Tangents: {mesh.tangents.Length > 0}");
report.AppendLine($" Has Colors: {mesh.colors.Length > 0}");
}
if (includeSubMeshes)
{
report.AppendLine($" SubMesh Count: {mesh.subMeshCount}");
for (int i = 0; i < mesh.subMeshCount; i++)
{
var subMesh = mesh.GetSubMesh(i);
report.AppendLine($" SubMesh {i}: {subMesh.indexCount / 3:N0} triangles");
}
}
if (includeBoneWeights)
{
var boneWeights = mesh.boneWeights;
report.AppendLine($" Bone Weights: {boneWeights.Length}");
report.AppendLine($" Bind Poses: {mesh.bindposes.Length}");
}
if (includeBlendShapes)
{
report.AppendLine($" Blend Shapes: {mesh.blendShapeCount}");
for (int i = 0; i < mesh.blendShapeCount; i++)
{
report.AppendLine($" {mesh.GetBlendShapeName(i)}: {mesh.GetBlendShapeFrameCount(i)} frames");
}
}
var memorySize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(mesh);
report.AppendLine($" Memory Usage: {FormatBytes(memorySize)}");
report.AppendLine($" Readable: {mesh.isReadable}");
report.AppendLine();
}
return report.ToString();
}
catch (Exception e)
{
return $"Error analyzing meshes: {e.Message}";
}
}
/// <summary>
/// Get detailed information for audio assets
/// </summary>
public static string GetAudioDetails(Dictionary<string, string> parameters)
{
try
{
var audioName = parameters.GetValueOrDefault("audioName", "");
var includeAll = parameters.GetValueOrDefault("includeAll", "false") == "true";
var includeCompressionInfo = parameters.GetValueOrDefault("includeCompressionInfo", "true") == "true";
var includeMetadata = parameters.GetValueOrDefault("includeMetadata", "true") == "true";
var audioClips = string.IsNullOrEmpty(audioName) && includeAll ?
Resources.FindObjectsOfTypeAll<AudioClip>() :
Resources.FindObjectsOfTypeAll<AudioClip>().Where(a =>
string.IsNullOrEmpty(audioName) || a.name.Contains(audioName)).ToArray();
var report = new StringBuilder();
report.AppendLine("=== Audio Clip Details ===");
report.AppendLine($"Found {audioClips.Length} audio clip(s)");
report.AppendLine();
foreach (var clip in audioClips.Take(20))
{
if (clip == null) continue;
report.AppendLine($"Audio Clip: {clip.name}");
report.AppendLine($" Length: {clip.length:F2} seconds");
report.AppendLine($" Channels: {clip.channels}");
report.AppendLine($" Frequency: {clip.frequency} Hz");
report.AppendLine($" Samples: {clip.samples:N0}");
report.AppendLine($" Load Type: {clip.loadType}");
report.AppendLine($" 3D: {!clip.ambisonic}");
if (includeCompressionInfo)
{
var assetPath = AssetDatabase.GetAssetPath(clip);
if (!string.IsNullOrEmpty(assetPath))
{
var importer = AssetImporter.GetAtPath(assetPath) as AudioImporter;
if (importer != null)
{
report.AppendLine($" Force Mono: {importer.forceToMono}");
var defaultSettings = importer.defaultSampleSettings;
report.AppendLine($" Preload Audio Data: {defaultSettings.loadType == AudioClipLoadType.CompressedInMemory}");
var settings = importer.defaultSampleSettings;
report.AppendLine($" Compression Format: {settings.compressionFormat}");
report.AppendLine($" Quality: {settings.quality}");
report.AppendLine($" Sample Rate: {settings.sampleRateSetting}");
}
}
}
var memorySize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(clip);
report.AppendLine($" Memory Usage: {FormatBytes(memorySize)}");
if (includeMetadata)
{
var fileInfo = GetAssetFileInfo(AssetDatabase.GetAssetPath(clip));
if (fileInfo != null)
{
report.AppendLine($" File Size: {FormatBytes(fileInfo.Length)}");
}
}
report.AppendLine();
}
return report.ToString();
}
catch (Exception e)
{
return $"Error analyzing audio clips: {e.Message}";
}
}
/// <summary>
/// Get detailed information for animation assets
/// </summary>
public static string GetAnimationDetails(Dictionary<string, string> parameters)
{
try
{
var animationName = parameters.GetValueOrDefault("animationName", "");
var includeAll = parameters.GetValueOrDefault("includeAll", "false") == "true";
var includeKeyframes = parameters.GetValueOrDefault("includeKeyframes", "true") == "true";
var includeEvents = parameters.GetValueOrDefault("includeEvents", "true") == "true";
var animationClips = string.IsNullOrEmpty(animationName) && includeAll ?
Resources.FindObjectsOfTypeAll<AnimationClip>() :
Resources.FindObjectsOfTypeAll<AnimationClip>().Where(a =>
string.IsNullOrEmpty(animationName) || a.name.Contains(animationName)).ToArray();
var report = new StringBuilder();
report.AppendLine("=== Animation Clip Details ===");
report.AppendLine($"Found {animationClips.Length} animation clip(s)");
report.AppendLine();
foreach (var clip in animationClips.Take(20))
{
if (clip == null) continue;
report.AppendLine($"Animation Clip: {clip.name}");
report.AppendLine($" Length: {clip.length:F3} seconds");
report.AppendLine($" Frame Rate: {clip.frameRate:F1} fps");
report.AppendLine($" Legacy: {clip.legacy}");
report.AppendLine($" Loop: {clip.isLooping}");
report.AppendLine($" Humanoid: {clip.isHumanMotion}");
if (includeEvents)
{
var events = AnimationUtility.GetAnimationEvents(clip);
report.AppendLine($" Animation Events: {events.Length}");
foreach (var evt in events.Take(5))
{
report.AppendLine($" {evt.time:F2}s: {evt.functionName}({evt.stringParameter})");
}
}
if (includeKeyframes)
{
var bindings = AnimationUtility.GetCurveBindings(clip);
report.AppendLine($" Curve Bindings: {bindings.Length}");
int totalKeyframes = 0;
foreach (var binding in bindings.Take(10))
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve != null)
{
totalKeyframes += curve.keys.Length;
report.AppendLine($" {binding.propertyName}: {curve.keys.Length} keys");
}
}
report.AppendLine($" Total Keyframes: {totalKeyframes:N0}");
}
var memorySize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(clip);
report.AppendLine($" Memory Usage: {FormatBytes(memorySize)}");
report.AppendLine();
}
return report.ToString();
}
catch (Exception e)
{
return $"Error analyzing animation clips: {e.Message}";
}
}
/// <summary>
/// Get detailed information for material assets
/// </summary>
public static string GetMaterialDetails(Dictionary<string, string> parameters)
{
try
{
var materialName = parameters.GetValueOrDefault("materialName", "");
var includeAll = parameters.GetValueOrDefault("includeAll", "false") == "true";
var includeShaderInfo = parameters.GetValueOrDefault("includeShaderInfo", "true") == "true";
var includeTextureReferences = parameters.GetValueOrDefault("includeTextureReferences", "true") == "true";
var includePropertyValues = parameters.GetValueOrDefault("includePropertyValues", "true") == "true";
var materials = string.IsNullOrEmpty(materialName) && includeAll ?
Resources.FindObjectsOfTypeAll<Material>() :
Resources.FindObjectsOfTypeAll<Material>().Where(m =>
string.IsNullOrEmpty(materialName) || m.name.Contains(materialName)).ToArray();
var report = new StringBuilder();
report.AppendLine("=== Material Details ===");
report.AppendLine($"Found {materials.Length} material(s)");
report.AppendLine();
foreach (var material in materials.Take(20))
{
if (material == null || material.shader == null) continue;
report.AppendLine($"Material: {material.name}");
if (includeShaderInfo)
{
report.AppendLine($" Shader: {material.shader.name}");
report.AppendLine($" Render Queue: {material.renderQueue}");
report.AppendLine($" Keywords: {string.Join(", ", material.shaderKeywords)}");
}
if (includeTextureReferences)
{
var texturePropertyNames = material.GetTexturePropertyNames();
report.AppendLine($" Textures ({texturePropertyNames.Length}):");
foreach (var propName in texturePropertyNames)
{
var texture = material.GetTexture(propName);
if (texture != null)
{
report.AppendLine($" {propName}: {texture.name} ({texture.width}x{texture.height})");
}
}
}
if (includePropertyValues)
{
// Color properties
var colorProps = GetShaderColorProperties(material.shader);
foreach (var prop in colorProps.Take(5))
{
var color = material.GetColor(prop);
report.AppendLine($" {prop}: RGBA({color.r:F2}, {color.g:F2}, {color.b:F2}, {color.a:F2})");
}
// Float properties
var floatProps = GetShaderFloatProperties(material.shader);
foreach (var prop in floatProps.Take(5))
{
var value = material.GetFloat(prop);
report.AppendLine($" {prop}: {value:F3}");
}
}
var memorySize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(material);
report.AppendLine($" Memory Usage: {FormatBytes(memorySize)}");
report.AppendLine();
}
return report.ToString();
}
catch (Exception e)
{
return $"Error analyzing materials: {e.Message}";
}
}
/// <summary>
/// Get asset file information
/// </summary>
public static string GetAssetFileInfo(Dictionary<string, string> parameters)
{
try
{
var assetPath = parameters.GetValueOrDefault("assetPath", "");
var assetType = parameters.GetValueOrDefault("assetType", "all");
var includeImportSettings = parameters.GetValueOrDefault("includeImportSettings", "true") == "true";
var includeMetadata = parameters.GetValueOrDefault("includeMetadata", "true") == "true";
var sortBy = parameters.GetValueOrDefault("sortBy", "name");
var assetGuids = string.IsNullOrEmpty(assetPath) ?
AssetDatabase.FindAssets(GetAssetTypeFilter(assetType)) :
new[] { AssetDatabase.AssetPathToGUID(assetPath) };
var assetInfos = new List<AssetFileInfo>();
foreach (var guid in assetGuids.Take(100)) // Limit for performance
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (string.IsNullOrEmpty(path)) continue;
var fileInfo = GetAssetFileInfo(path);
if (fileInfo != null)
{
var assetInfo = new AssetFileInfo
{
Path = path,
Name = Path.GetFileName(path),
Size = fileInfo.Length,
CreationTime = fileInfo.CreationTime,
LastWriteTime = fileInfo.LastWriteTime,
Extension = Path.GetExtension(path)
};
if (includeImportSettings)
{
var importer = AssetImporter.GetAtPath(path);
assetInfo.ImporterType = importer?.GetType().Name ?? "Unknown";
}
assetInfos.Add(assetInfo);
}
}
// Sort results
assetInfos = SortAssetInfos(assetInfos, sortBy);
return FormatAssetFileReport(assetInfos, includeMetadata);
}
catch (Exception e)
{
return $"Error getting asset file info: {e.Message}";
}
}
/// <summary>
/// Analyze asset usage
/// </summary>
public static string AnalyzeAssetUsage(Dictionary<string, string> parameters)
{
try
{
var assetType = parameters.GetValueOrDefault("assetType", "all");
var findUnused = parameters.GetValueOrDefault("findUnused", "true") == "true";
var findDuplicates = parameters.GetValueOrDefault("findDuplicates", "false") == "true";
var includeSceneReferences = parameters.GetValueOrDefault("includeSceneReferences", "true") == "true";
var includePrefabReferences = parameters.GetValueOrDefault("includePrefabReferences", "true") == "true";
var report = new StringBuilder();
report.AppendLine("=== Asset Usage Analysis ===");
var assetGuids = AssetDatabase.FindAssets(GetAssetTypeFilter(assetType));
report.AppendLine($"Analyzing {assetGuids.Length} assets of type '{assetType}'");
report.AppendLine();
if (findUnused)
{
var unusedAssets = FindUnusedAssets(assetGuids, includeSceneReferences, includePrefabReferences);
report.AppendLine($"=== Unused Assets ({unusedAssets.Count}) ===");
foreach (var asset in unusedAssets.Take(20))
{
report.AppendLine($" {asset}");
}
report.AppendLine();
}
if (findDuplicates)
{
var duplicates = FindPotentialDuplicates(assetGuids);
report.AppendLine($"=== Potential Duplicates ({duplicates.Count}) ===");
foreach (var group in duplicates.Take(10))
{
report.AppendLine($" Similar names:");
foreach (var asset in group)
{
report.AppendLine($" {asset}");
}
report.AppendLine();
}
}
return report.ToString();
}
catch (Exception e)
{
return $"Error analyzing asset usage: {e.Message}";
}
}
// ===== Helper Methods =====
private static int GetUVChannelCount(Mesh mesh)
{
int count = 0;
var uvs = new List<Vector2>();
for (int i = 0; i < 8; i++)
{
mesh.GetUVs(i, uvs);
if (uvs.Count > 0) count++;
uvs.Clear();
}
return count;
}
private static string[] GetShaderColorProperties(Shader shader)
{
var properties = new List<string>();
int count = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < count; i++)
{
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Color)
{
properties.Add(ShaderUtil.GetPropertyName(shader, i));
}
}
return properties.ToArray();
}
private static string[] GetShaderFloatProperties(Shader shader)
{
var properties = new List<string>();
int count = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < count; i++)
{
var type = ShaderUtil.GetPropertyType(shader, i);
if (type == ShaderUtil.ShaderPropertyType.Float || type == ShaderUtil.ShaderPropertyType.Range)
{
properties.Add(ShaderUtil.GetPropertyName(shader, i));
}
}
return properties.ToArray();
}
private static FileInfo GetAssetFileInfo(string assetPath)
{
try
{
var fullPath = Path.Combine(Application.dataPath, assetPath.Substring(7)); // Remove "Assets/"
return new FileInfo(fullPath);
}
catch
{
return null;
}
}
private static string GetAssetTypeFilter(string assetType)
{
return assetType switch
{
"textures" => "t:Texture2D",
"meshes" => "t:Mesh",
"audio" => "t:AudioClip",
"scripts" => "t:MonoScript",
"prefabs" => "t:Prefab",
"materials" => "t:Material",
_ => ""
};
}
private static List<AssetFileInfo> SortAssetInfos(List<AssetFileInfo> assetInfos, string sortBy)
{
return sortBy switch
{
"size" => assetInfos.OrderByDescending(a => a.Size).ToList(),
"date" => assetInfos.OrderByDescending(a => a.LastWriteTime).ToList(),
"type" => assetInfos.OrderBy(a => a.Extension).ToList(),
_ => assetInfos.OrderBy(a => a.Name).ToList()
};
}
private static List<string> FindUnusedAssets(string[] assetGuids, bool includeScenes, bool includePrefabs)
{
var unusedAssets = new List<string>();
var dependencies = AssetDatabase.GetDependencies(AssetDatabase.GetAllAssetPaths(), true);
foreach (var guid in assetGuids.Take(50)) // Limit for performance
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (string.IsNullOrEmpty(path)) continue;
// Simple check - if asset is not in dependencies, it might be unused
if (!dependencies.Contains(path))
{
unusedAssets.Add(path);
}
}
return unusedAssets;
}
private static List<List<string>> FindPotentialDuplicates(string[] assetGuids)
{
var duplicates = new List<List<string>>();
var nameGroups = new Dictionary<string, List<string>>();
foreach (var guid in assetGuids.Take(100))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (string.IsNullOrEmpty(path)) continue;
var name = Path.GetFileNameWithoutExtension(path).ToLower();
if (!nameGroups.ContainsKey(name))
{
nameGroups[name] = new List<string>();
}
nameGroups[name].Add(path);
}
foreach (var group in nameGroups.Values)
{
if (group.Count > 1)
{
duplicates.Add(group);
}
}
return duplicates;
}
private static string FormatAssetFileReport(List<AssetFileInfo> assetInfos, bool includeMetadata)
{
var report = new StringBuilder();
report.AppendLine("=== Asset File Information ===");
report.AppendLine($"Found {assetInfos.Count} asset(s)");
report.AppendLine();
long totalSize = 0;
foreach (var asset in assetInfos.Take(50))
{
report.AppendLine($"Asset: {asset.Name}");
report.AppendLine($" Path: {asset.Path}");
report.AppendLine($" Size: {FormatBytes(asset.Size)}");
report.AppendLine($" Type: {asset.Extension}");
if (includeMetadata)
{
report.AppendLine($" Created: {asset.CreationTime:yyyy-MM-dd HH:mm:ss}");
report.AppendLine($" Modified: {asset.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
report.AppendLine($" Importer: {asset.ImporterType}");
}
totalSize += asset.Size;
report.AppendLine();
}
report.AppendLine($"Total Size: {FormatBytes(totalSize)}");
return report.ToString();
}
private static string FormatBytes(long bytes)
{
string[] suffixes = { "B", "KB", "MB", "GB" };
int counter = 0;
decimal number = (decimal)bytes;
while (Math.Round(number / 1024) >= 1)
{
number = number / 1024;
counter++;
}
return string.Format("{0:n1} {1}", number, suffixes[counter]);
}
private class AssetFileInfo
{
public string Path { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public DateTime CreationTime { get; set; }
public DateTime LastWriteTime { get; set; }
public string Extension { get; set; }
public string ImporterType { get; set; }
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 639c91d87c2894f878fe7f014cd43212
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusAssetAnalyzer.cs
uploadId: 920982
@@ -1,583 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEditor;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Arbitrary C# evaluation for the Editor — equivalent of Blender's
/// run_python tool. Wraps Mono.CSharp.Evaluator (instance API) so callers
/// can execute any C# snippet against the running Editor without
/// triggering an AssemblyReload.
///
/// Static Evaluator.Init/Run on Unity 2022.3+ silently no-ops; the real
/// path is `new Evaluator(new CompilerContext(new CompilerSettings(),
/// new ConsoleReportPrinter()))` plus injecting every assembly already
/// loaded in the AppDomain so UnityEngine / UnityEditor / Newtonsoft.Json
/// resolve.
///
/// Expressions ("1+1", "GameObject.Find(\"X\").name") must NOT end with
/// a semicolon — those are evaluated via Evaluate(...). Statements
/// ("var x = 1; Debug.Log(x);") run through Run(...).
/// </summary>
public static class NexusCSharpEval
{
private static object _evaluator;
private static MethodInfo _evaluateMethod;
private static MethodInfo _runMethod;
private static MethodInfo _referenceAssemblyMethod;
private static StringBuilder _captured = new StringBuilder();
private static readonly object _lock = new object();
// ESC-0107 fix (E): capture Unity Debug.Log output that Console.SetOut
// can't catch (UnityEngine.Debug routes through Unity Console, not
// managed Console.Out). Subscribe to Application.logMessageReceived
// while a Run call is in progress.
private static bool _captureUnityLogs = false;
private static Application.LogCallback _logCallback;
// ESC-0107 fix (D, revised): receive the return value from `return X;`
// snippets via a static field. The user's `return X;` is rewritten to
// `SynapticPro.NexusCSharpEval.__SetResult(X);` and executed through
// Evaluator.Run, which lets Mono.CSharp's normal method-body parser
// handle the expression (no pointer-type ambiguity, no Evaluate
// restrictions). We then read the field back in managed code.
public static object __LastResult;
public static bool __LastResultSet;
public static void __SetResult(object value)
{
__LastResult = value;
__LastResultSet = true;
}
public static string Run(Dictionary<string, string> parameters)
{
var code = parameters != null && parameters.TryGetValue("code", out var c) ? c : "";
if (string.IsNullOrEmpty(code))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "code parameter is required",
example = "GameObject.Find(\"Cube\")?.name"
});
}
lock (_lock)
{
try
{
if (!EnsureInitialized(out var initError))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = initError
});
}
_captured.Length = 0;
var oldOut = Console.Out;
Console.SetOut(new StringWriter(_captured));
// Hook Unity Debug.Log into the capture buffer (ESC-0107 fix E).
AttachUnityLogCapture();
try
{
// ESC-0107 fix D (revised again): rewrite the trailing
// `return X;` into a call to our static `__SetResult(X)`
// sink, then Run the whole thing as statements. This
// sidesteps two Mono.CSharp quirks:
// - Run(...) discards real `return` values
// - Evaluate("x * 1") mis-parses var*literal as a
// pointer-type declaration, returning resultSet=false
// Inside a normal Run body the parser treats `*` as
// unambiguous multiplication and `__SetResult(X)` as a
// regular method call. The receiver field is read back
// in managed code after Run completes.
var trimmed = code.TrimEnd();
SplitReturnTail(trimmed, out var prefixStatements, out var returnExpression);
// For bare expressions (no `;`) we still use Evaluate
// — it works fine for that case and avoids a needless
// method-call wrap.
string expressionToEvaluate = null;
string rewrittenStatements = null;
if (returnExpression != null)
{
rewrittenStatements = string.IsNullOrEmpty(prefixStatements)
? $"SynapticPro.NexusCSharpEval.__SetResult({returnExpression});"
: $"{prefixStatements} SynapticPro.NexusCSharpEval.__SetResult({returnExpression});";
}
else if (!trimmed.EndsWith(";"))
{
expressionToEvaluate = code;
}
// Reset the sink before every call so a stale value
// from a previous run can't leak through.
__LastResult = null;
__LastResultSet = false;
if (rewrittenStatements != null && _runMethod != null)
{
// First try plain Run. Works for most snippets and
// is the cheapest path. If it succeeds but doesn't
// set the result (e.g. the user's code contains
// generic type syntax `List<int>` which Mono.CSharp
// Evaluator's top-level parser chokes on), retry
// wrapped in an immediately-invoked Action lambda
// — Mono parses the lambda body with the regular
// method-body parser, which handles generics fine.
object runResultR;
try { runResultR = _runMethod.Invoke(_evaluator, new object[] { rewrittenStatements }); }
catch (TargetInvocationException tie)
{
return Error(tie.InnerException ?? tie);
}
bool runOkR = runResultR is bool rbR && rbR;
if (!__LastResultSet)
{
// Reset for the wrapped retry (output already
// captured what the failed attempt printed,
// which should be nothing — Run prints nothing
// on parse failure).
__LastResult = null;
__LastResultSet = false;
var wrapped = $"((System.Action)(() => {{ {rewrittenStatements} }}))();";
try { runResultR = _runMethod.Invoke(_evaluator, new object[] { wrapped }); }
catch (TargetInvocationException tie)
{
return Error(tie.InnerException ?? tie);
}
runOkR = runResultR is bool rbR2 && rbR2;
}
return JsonConvert.SerializeObject(new
{
success = runOkR,
output = _captured.ToString(),
result = __LastResultSet ? SafeStringify(__LastResult) : null,
resultSet = __LastResultSet
});
}
if (expressionToEvaluate != null && _evaluateMethod != null)
{
var args = new object[] { expressionToEvaluate, null, false };
object remainderObj = null;
try { remainderObj = _evaluateMethod.Invoke(_evaluator, args); }
catch (TargetInvocationException tie)
{
return Error(tie.InnerException ?? tie);
}
string remainder = remainderObj as string ?? "";
object result = args[1];
bool resultSet = args[2] is bool b && b;
if (string.IsNullOrEmpty(remainder))
{
return JsonConvert.SerializeObject(new
{
success = true,
output = _captured.ToString(),
result = resultSet ? SafeStringify(result) : null,
resultSet
});
}
// Evaluator returned remainder — the "expression" we
// extracted was actually parsed as statements. Fall
// through to statement-only path for the full input.
}
// Pure-statement input (no return form, no bare expression)
// — fall through to plain Run. Captures stdout but no
// value. Note: with the lambda-wrap approach above we
// never executed the prefix as a side effect already,
// so the original `code` is safe to Run here as-is.
// Statement mode.
if (_runMethod == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Evaluator.Run method not found on this Mono.CSharp build"
});
}
object runResult;
try { runResult = _runMethod.Invoke(_evaluator, new object[] { code }); }
catch (TargetInvocationException tie)
{
return Error(tie.InnerException ?? tie);
}
bool runOk = runResult is bool rb && rb;
// Also surface __SetResult writes from user code, even
// when we didn't auto-rewrite. Lets advanced callers do
// their own SetResult invocation (e.g. inside lambdas
// when avoiding the Evaluator generic-parse bug).
if (__LastResultSet)
{
return JsonConvert.SerializeObject(new
{
success = runOk,
output = _captured.ToString(),
result = SafeStringify(__LastResult),
resultSet = true
});
}
return JsonConvert.SerializeObject(new
{
success = runOk,
output = _captured.ToString(),
result = (object)null
});
}
finally
{
Console.SetOut(oldOut);
DetachUnityLogCapture();
}
}
catch (Exception e)
{
return Error(e);
}
}
}
private static bool EnsureInitialized(out string error)
{
error = null;
if (_evaluator != null) return true;
Assembly mcs = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "Mono.CSharp");
if (mcs == null)
{
try { mcs = Assembly.Load("Mono.CSharp"); } catch { /* try alt below */ }
}
if (mcs == null)
{
error = "Mono.CSharp.dll is not loaded in this Unity build. " +
"Add an .asmdef reference to Mono.CSharp or use a Unity " +
"version that bundles it (most Unity LTS releases do).";
return false;
}
Type settingsType = mcs.GetType("Mono.CSharp.CompilerSettings");
Type printerType = mcs.GetType("Mono.CSharp.ConsoleReportPrinter");
Type reportType = mcs.GetType("Mono.CSharp.Report");
Type contextType = mcs.GetType("Mono.CSharp.CompilerContext");
Type evalType = mcs.GetType("Mono.CSharp.Evaluator");
if (settingsType == null || printerType == null || contextType == null || evalType == null)
{
error = "Mono.CSharp internal types missing " +
$"(settings={settingsType != null}, printer={printerType != null}, " +
$"context={contextType != null}, eval={evalType != null}).";
return false;
}
object settings = Activator.CreateInstance(settingsType);
object printer = Activator.CreateInstance(printerType);
// Try CompilerContext(CompilerSettings, ReportPrinter)
object ctx = null;
ConstructorInfo ctxCtor = contextType.GetConstructors()
.FirstOrDefault(ci => ci.GetParameters().Length == 2);
if (ctxCtor != null)
{
try { ctx = ctxCtor.Invoke(new object[] { settings, printer }); }
catch { ctx = null; }
}
if (ctx == null)
{
error = "Could not construct Mono.CSharp.CompilerContext.";
return false;
}
ConstructorInfo evalCtor = evalType.GetConstructor(new[] { contextType });
if (evalCtor == null)
{
error = "Mono.CSharp.Evaluator(CompilerContext) constructor not found.";
return false;
}
_evaluator = evalCtor.Invoke(new object[] { ctx });
_evaluateMethod = evalType.GetMethod(
"Evaluate",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string), typeof(object).MakeByRefType(), typeof(bool).MakeByRefType() },
null);
_runMethod = evalType.GetMethod(
"Run",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string) },
null);
_referenceAssemblyMethod = evalType.GetMethod(
"ReferenceAssembly",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(Assembly) },
null);
// Inject every already-loaded assembly so user code can reach
// UnityEngine / UnityEditor / Newtonsoft.Json / the project's
// own scripts without manual `using`.
if (_referenceAssemblyMethod != null)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
if (asm.IsDynamic) continue;
if (string.IsNullOrEmpty(asm.Location)) continue;
_referenceAssemblyMethod.Invoke(_evaluator, new object[] { asm });
}
catch { /* skip individual failures */ }
}
}
// Pre-import common namespaces.
if (_runMethod != null)
{
try
{
_runMethod.Invoke(_evaluator, new object[]
{
"using System; " +
"using System.Collections.Generic; " +
"using System.Linq; " +
"using System.IO; " +
"using UnityEngine; " +
"using UnityEditor; " +
"using Newtonsoft.Json;"
});
}
catch { /* best-effort */ }
}
return true;
}
private static string Error(Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message,
stackTrace = e.StackTrace
});
}
private static object SafeStringify(object value)
{
if (value == null) return null;
try
{
var t = value.GetType();
if (t.IsPrimitive || value is string) return value;
if (typeof(UnityEngine.Object).IsAssignableFrom(t)) return value.ToString();
return JsonConvert.SerializeObject(value);
}
catch
{
return value?.ToString();
}
}
/// <summary>
/// Split `[statements...] return X;` into (prefix, expression).
/// Locates the LAST top-level `return` keyword (depth 0 from braces,
/// parens, brackets, strings and comments) and splits there.
///
/// Earlier implementations split on the last top-level `;` and then
/// checked that the resulting final statement started with `return`.
/// That broke on inputs like `foreach (var x in xs) { X(); } return Y;`
/// because the only top-level `;` is the trailing one (the `X();`
/// inside braces is at depth 1) so the "final statement" became the
/// entire body including the foreach — not starting with `return`.
///
/// The new approach: scan for `return` itself at depth 0, take
/// everything before it as the prefix (its predecessor will end in
/// `;` or `}` — both are valid statement terminators that Mono.CSharp
/// Evaluator.Run accepts), take everything between `return` and the
/// trailing `;` as the expression.
/// </summary>
private static void SplitReturnTail(string trimmed, out string prefix, out string expression)
{
prefix = "";
expression = null;
if (string.IsNullOrEmpty(trimmed)) return;
if (!trimmed.EndsWith(";")) return;
var withoutSemi = trimmed.Substring(0, trimmed.Length - 1).TrimEnd();
int returnIdx = FindLastTopLevelReturnKeyword(withoutSemi);
if (returnIdx < 0) return;
// Expression is the slice after the `return` keyword.
var expr = withoutSemi.Substring(returnIdx + "return".Length).Trim();
if (string.IsNullOrEmpty(expr)) return;
expression = expr;
if (returnIdx > 0)
{
// Prefix is everything before `return`. Validate it terminates
// with `;` or `}` — anything else (e.g. half-written input)
// would be malformed, in which case skip the rewrite.
var candidatePrefix = withoutSemi.Substring(0, returnIdx).TrimEnd();
if (!candidatePrefix.EndsWith(";") && !candidatePrefix.EndsWith("}"))
{
expression = null;
return;
}
prefix = candidatePrefix;
}
}
/// <summary>
/// Find the start index of the LAST occurrence of the `return`
/// keyword in <paramref name="s"/> that sits at the top level
/// (outside any (), [], {} group, and outside string/char/comment
/// literals). Also enforces token boundaries so `Return`,
/// `myReturn`, `returnX` etc. don't match.
///
/// Returns -1 when no such keyword exists.
/// </summary>
private static int FindLastTopLevelReturnKeyword(string s)
{
const string KW = "return";
int paren = 0, bracket = 0, brace = 0;
int lastReturn = -1;
int i = 0;
while (i < s.Length)
{
char c = s[i];
// Line comment // ...
if (c == '/' && i + 1 < s.Length && s[i + 1] == '/')
{
while (i < s.Length && s[i] != '\n') i++;
continue;
}
// Block comment /* ... */
if (c == '/' && i + 1 < s.Length && s[i + 1] == '*')
{
i += 2;
while (i + 1 < s.Length && !(s[i] == '*' && s[i + 1] == '/')) i++;
i += 2;
continue;
}
// String / verbatim / interpolated — skip until closing quote.
if (c == '"' || c == '\'')
{
char quote = c;
bool verbatim = i > 0 && (s[i - 1] == '@' || s[i - 1] == '$');
i++;
while (i < s.Length)
{
if (!verbatim && s[i] == '\\') { i += 2; continue; }
if (s[i] == quote)
{
if (verbatim && i + 1 < s.Length && s[i + 1] == quote) { i += 2; continue; }
i++;
break;
}
i++;
}
continue;
}
if (c == '(') { paren++; i++; continue; }
if (c == ')') { paren--; i++; continue; }
if (c == '[') { bracket++; i++; continue; }
if (c == ']') { bracket--; i++; continue; }
if (c == '{') { brace++; i++; continue; }
if (c == '}') { brace--; i++; continue; }
// Check for `return` keyword start at this position.
if (paren == 0 && bracket == 0 && brace == 0 &&
c == KW[0] && i + KW.Length <= s.Length &&
s.Substring(i, KW.Length) == KW)
{
// Left boundary: must be start of string OR a non-identifier
// char (whitespace, `}`, `;`, `{`, etc.).
bool leftOk = (i == 0) || !IsIdentChar(s[i - 1]);
// Right boundary: must be whitespace or `(` after the keyword.
bool rightOk = false;
if (i + KW.Length < s.Length)
{
var nxt = s[i + KW.Length];
rightOk = !IsIdentChar(nxt);
}
else
{
rightOk = true; // EOF immediately after — `return` with no expr (handled later)
}
if (leftOk && rightOk)
{
lastReturn = i;
i += KW.Length;
continue;
}
}
i++;
}
return lastReturn;
}
private static bool IsIdentChar(char c)
{
return c == '_' || char.IsLetterOrDigit(c);
}
private static void AttachUnityLogCapture()
{
if (_captureUnityLogs) return;
_captureUnityLogs = true;
_logCallback = (string condition, string stackTrace, LogType type) =>
{
// Mirror Debug.Log / LogWarning / LogError into _captured so the
// caller sees what their script printed (NexusCSharpEval is the
// only writer to _captured during a Run call, no contention).
try
{
var prefix = type == LogType.Error || type == LogType.Exception ? "[error] "
: type == LogType.Warning ? "[warn] "
: "";
_captured.Append(prefix).Append(condition).Append('\n');
}
catch { /* best-effort, never throw from log hook */ }
};
Application.logMessageReceived += _logCallback;
}
private static void DetachUnityLogCapture()
{
if (!_captureUnityLogs) return;
try
{
if (_logCallback != null) Application.logMessageReceived -= _logCallback;
}
catch { }
_captureUnityLogs = false;
_logCallback = null;
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 4807fcd26b9e2497388d1c548cb48f8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusCSharpEval.cs
uploadId: 920982
@@ -1,746 +0,0 @@
using UnityEditor;
using UnityEngine;
using SynapticAIPro;
namespace SynapticPro
{
/// <summary>
/// Changelog dialog shown on first import or version update
/// </summary>
[InitializeOnLoad]
public class NexusChangelogWindow : EditorWindow
{
private static string CURRENT_VERSION => NexusVersion.Current;
private const string PREF_KEY_LAST_VERSION = "SynapticPro_LastShownVersion";
private const string PREF_KEY_DONT_SHOW = "SynapticPro_DontShowChangelog";
private const string PREF_KEY_LANGUAGE = "SynapticPro_ChangelogLanguage";
private enum Language { English, Japanese }
private static Language currentLanguage = Language.English;
private static bool dontShowAgain = false;
private Vector2 scrollPosition;
static NexusChangelogWindow()
{
EditorApplication.delayCall += ShowOnStartupIfNeeded;
}
private static void ShowOnStartupIfNeeded()
{
// Don't show during play mode
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
// Check if user disabled
if (EditorPrefs.GetBool(PREF_KEY_DONT_SHOW, false))
return;
// Check if already shown for this version
string lastVersion = EditorPrefs.GetString(PREF_KEY_LAST_VERSION, "");
if (lastVersion == CURRENT_VERSION)
return;
// Show dialog
ShowWindow();
// Mark as shown
EditorPrefs.SetString(PREF_KEY_LAST_VERSION, CURRENT_VERSION);
}
[MenuItem("Tools/Synaptic Pro/What's New", false, 100)]
public static void ShowWindow()
{
var window = GetWindow<NexusChangelogWindow>(true, "Synaptic AI Pro - What's New", true);
window.minSize = new Vector2(500, 450);
window.maxSize = new Vector2(600, 650);
window.ShowUtility();
}
private void OnEnable()
{
dontShowAgain = EditorPrefs.GetBool(PREF_KEY_DONT_SHOW, false);
currentLanguage = (Language)EditorPrefs.GetInt(PREF_KEY_LANGUAGE, 0);
}
private void OnGUI()
{
// Language selector (top right)
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label(currentLanguage == Language.English ? "Language:" : "言語:", GUILayout.Width(60));
var newLang = (Language)EditorGUILayout.Popup((int)currentLanguage, new string[] { "English", "日本語" }, GUILayout.Width(80));
if (newLang != currentLanguage)
{
currentLanguage = newLang;
EditorPrefs.SetInt(PREF_KEY_LANGUAGE, (int)currentLanguage);
}
EditorGUILayout.EndHorizontal();
// Header
GUILayout.Space(5);
var headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 20,
alignment = TextAnchor.MiddleCenter
};
GUILayout.Label($"Synaptic AI Pro v{CURRENT_VERSION}", headerStyle);
GUILayout.Space(5);
var subtitleStyle = new GUIStyle(EditorStyles.label)
{
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Italic
};
GUILayout.Label(L("What's New", "更新内容"), subtitleStyle);
GUILayout.Space(15);
// Changelog content
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
DrawChangelogContent();
EditorGUILayout.EndScrollView();
GUILayout.Space(10);
// Don't show again toggle
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
bool newDontShow = EditorGUILayout.ToggleLeft(
L("Don't show on startup", "起動時に表示しない"),
dontShowAgain, GUILayout.Width(180));
if (newDontShow != dontShowAgain)
{
dontShowAgain = newDontShow;
EditorPrefs.SetBool(PREF_KEY_DONT_SHOW, dontShowAgain);
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(5);
// Buttons
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button(L("Open Setup", "セットアップを開く"), GUILayout.Width(120), GUILayout.Height(30)))
{
NexusMCPSetupWindow.ShowWindow();
Close();
}
GUILayout.Space(10);
if (GUILayout.Button(L("Close", "閉じる"), GUILayout.Width(80), GUILayout.Height(30)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
}
// Localization helper
private string L(string en, string ja)
{
return currentLanguage == Language.Japanese ? ja : en;
}
private void DrawChangelogContent()
{
var sectionStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 14
};
var itemStyle = new GUIStyle(EditorStyles.label)
{
wordWrap = true,
richText = true
};
// v1.2.23
GUILayout.Label(L("v1.2.23 - run_csharp result capture + HTTP server stability", "v1.2.23 - run_csharp 戻り値捕捉 + HTTP サーバー安定化"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix (ESC-0107): run_csharp returned result:null for most snippets</b>", "<b>★ 修正 (ESC-0107): run_csharp が殆どの snippet で result:null を返していた問題</b>"), itemStyle);
GUILayout.Label(L("• Mono.CSharp.Evaluator.Run discards return values. We now detect the last top-level return keyword (depth-aware) and rewrite `return X;` into a static-field sink call, capturing X across multi-statement bodies / foreach{}/if{} blocks / multiplication expressions.", "• Mono.CSharp.Evaluator.Run は戻り値を破棄するため、最後の top-level return キーワードを深度考慮で検出し、`return X;` を静的フィールド経由の sink 呼び出しに書き換え。複文 / foreach { } / if { } / 乗算式すべてで X が捕捉されるようになった"), itemStyle);
GUILayout.Label(L("• Application.logMessageReceived hook added so Debug.Log / LogWarning / LogError lines appear in the `output` field (was missing — only Console.Out was captured)", "• Application.logMessageReceived フック追加。Debug.Log / LogWarning / LogError が output フィールドに反映 (従来は Console.Out のみで取得漏れ)"), itemStyle);
GUILayout.Label(L("• Known limitation: Mono parser cannot instantiate generic TYPES (new List&lt;int&gt;() etc). Workaround: use arrays / ArrayList / generic methods (FindObjectsByType&lt;T&gt;)", "• 既知制限: Mono パーサが generic 型インスタンス化 (new List&lt;int&gt;() 等) を解釈できない。回避策は配列 / ArrayList / generic メソッド (FindObjectsByType&lt;T&gt;) 利用"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix (ESC-0108): HTTP server WebSocket dropped every ~30s</b>", "<b>★ 修正 (ESC-0108): HTTP サーバー WebSocket が約30秒で切断</b>"), itemStyle);
GUILayout.Label(L("• Mono ClientWebSocket doesn't auto-pong protocol-level pings (unlike .NET 5+). Node side terminated the connection every heartbeat interval", "• Mono ClientWebSocket は .NET 5+ と異なり protocol-level ping に自動 pong しない。結果 Node 側が毎ハートビートで切断"), itemStyle);
GUILayout.Label(L("• http-server.js heartbeat now uses last-message-seen timestamps. Configurable via UNITY_STALE_TIMEOUT_MS env (default 60s)", "• http-server.js の heartbeat を last-message-seen 方式に置換。UNITY_STALE_TIMEOUT_MS 環境変数で調整可 (デフォルト 60s)"), itemStyle);
GUILayout.Label(L("• Reported and verified by xvpower. — thank you!", "• xvpower. さん報告・検証ありがとうございます"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: HTTP server died on macOS/Linux during domain reload</b>", "<b>★ 修正: macOS/Linux で domain reload のたびに HTTP サーバーが死亡</b>"), itemStyle);
GUILayout.Label(L("• Previous Process.Start with piped stdout/stderr caused node to hit SIGPIPE when Unity's C# domain reloaded the pipe readers", "• 旧 Process.Start は stdout/stderr を C# 側 pipe にリダイレクトしており、domain reload で pipe reader が消えて node が SIGPIPE で死亡"), itemStyle);
GUILayout.Label(L("• Replaced with `nohup node ... >log 2>&1 </dev/null &` detach. Process is independent of Unity's lifecycle, survives all recompiles", "• `nohup node ... >log 2>&1 </dev/null &` 経由の detach に変更。Unity ライフサイクルから独立、recompile で死なない"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: Auto Reconnect didn't engage on fresh installs</b>", "<b>★ 修正: 新規インストール時に Auto Reconnect が機能しなかった問題</b>"), itemStyle);
GUILayout.Label(L("• enableMCP default flipped from false → true. Unity is always a CLIENT of the MCP server (port 8090), the opt-in flag was a UX trap", "• enableMCP デフォルトを false → true に変更。Unity は常に MCP サーバー (port 8090) のクライアントなので opt-in 必要なフラグは UX トラップだった"), itemStyle);
GUILayout.Label(L("• Manual AI Reconnect, Auto Reconnect toggle, and successful connect all force enableMCP=true so it persists across domain reloads", "• 手動 AI Reconnect、Auto Reconnect トグル、接続成功時のいずれでも enableMCP=true を永続化、domain reload を跨いで維持"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Added: AI Connection tab connection-controls bar</b>", "<b>★ 追加: AI Connection タブに接続コントロールバー</b>"), itemStyle);
GUILayout.Label(L("• Live MCP status indicator + `AI Reconnect` button (silent) + `Auto Reconnect` checkbox + `Discord` shortcut. Surfaces Tools-menu items in the Setup window where users actually troubleshoot", "• MCP 接続ライブステータス + `AI Reconnect` ボタン (確認ダイアログなし) + `Auto Reconnect` チェックボックス + `Discord` ショートカット。Tools メニュー項目をユーザーが普段デバッグする Setup Window 内に集約"), itemStyle);
GUILayout.Label(L("• MCP Server: Start/Stop kept in Tools menu (advanced)", "• MCP Server: Start/Stop は引き続き Tools メニュー (上級向け)"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: port-mapping JSON corruption infinite log loop</b>", "<b>★ 修正: port-mapping JSON 破損による Console ログ無限ループ</b>"), itemStyle);
GUILayout.Label(L("• NexusProjectPortManager.LoadMapping recovery now deletes .backup before File.Move, then writes a fresh empty mapping. Previously the silent catch left the corrupt file in place, causing frame-rate Console spam after any concurrent write race", "• NexusProjectPortManager.LoadMapping の復旧処理を強化。.backup を事前削除し、復旧後すぐ空の有効 JSON を書き出す。従来は silent catch で破損ファイルが残存し書き込み競合後に毎フレーム Console エラーが出続けていた"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.22
GUILayout.Label(L("v1.2.22 - EMERGENCY HOTFIX: MCP timeout (ESC-0102)", "v1.2.22 - 緊急修正: MCP timeout 問題 (ESC-0102)"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Critical fix: MCP timeout after v1.2.20/21 update</b>", "<b>★ 重要修正: v1.2.20/21 アップデート後の MCP タイムアウト</b>"), itemStyle);
GUILayout.Label(L("• SynLog.Info called EditorPrefs.GetBool on every log — main-thread only, threw silently on background WebSocket Receive thread and killed the listener Task", "• SynLog.Info が毎回 EditorPrefs.GetBool を呼び、これがメインスレッド限定だったため WebSocket 受信スレッドで例外を投げ、リスナータスクが silent kill されていた"), itemStyle);
GUILayout.Label(L("• Fix: SynLog now caches verbose flag in a volatile bool, initialized via [InitializeOnLoadMethod]", "• 修正: SynLog の verbose flag を volatile bool でキャッシュ、[InitializeOnLoadMethod] で初期化"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: NexusEditorMCPService reconnect storm</b>", "<b>★ 修正: NexusEditorMCPService 再接続ストーム</b>"), itemStyle);
GUILayout.Label(L("• lastConnectionCheckTime was written via Stopwatch-since-classload but compared against Time.realtimeSinceStartup. After domain reload Stopwatch reset to 0 → reconnect gate always true → reconnects every frame", "• lastConnectionCheckTime が Stopwatch ベースで書き込まれ、Time.realtimeSinceStartup で読まれていた。ドメインリロード後に Stopwatch=0 になり差分が常に大きく、毎フレーム再接続ループ発生"), itemStyle);
GUILayout.Label(L("• Fix: ThreadSafeTime() now calibrates against Time.realtimeSinceStartup on first main-thread tick", "• 修正: ThreadSafeTime() を初回 main-thread tick で Time.realtimeSinceStartup と同期キャリブレーション"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ New: unity_run_csharp tool (equivalent of Blender's run_python)</b>", "<b>★ 新規: unity_run_csharp ツール (Blender run_python 相当)</b>"), itemStyle);
GUILayout.Label(L("• Execute arbitrary C# against the running Editor — UnityEngine / UnityEditor / Linq / Newtonsoft.Json pre-imported", "• 任意 C# を Editor 内で実行可能。UnityEngine / UnityEditor / Linq / Newtonsoft.Json プリインポート済み"), itemStyle);
GUILayout.Label(L("• Uses Mono.CSharp.Evaluator instance API + AppDomain assembly injection, does NOT trigger AssemblyReload", "• Mono.CSharp.Evaluator のインスタンス API + AppDomain アセンブリ注入。AssemblyReload を起こさない"), itemStyle);
GUILayout.Label(L("• Promoted to a SuperSave top-level meta-tool for direct invocation", "• SuperSave のトップレベル meta-tool に昇格、直接呼び出し可"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Diagnostics</b>", "<b>★ 診断改善</b>"), itemStyle);
GUILayout.Label(L("• index-supersave.js: ws error handlers, send callback, connection diagnostics", "• index-supersave.js: ws エラーハンドラ、send コールバック、接続診断ログ追加"), itemStyle);
GUILayout.Label(L("• NexusWebSocketClient.ReceiveLoop (HTTP path): fixed missing EndOfMessage concatenation that truncated >4KB messages", "• NexusWebSocketClient.ReceiveLoop (HTTP 経路): 4KB 超メッセージが切れる EndOfMessage 連結欠落バグ修正"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.21
GUILayout.Label(L("v1.2.21 - Windows HTTP Server Cascade Kill Fix (ESC-0095)", "v1.2.21 - Windows HTTP サーバー連鎖死問題の修正 (ESC-0095)"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Root cause finally identified</b>", "<b>★ 長年未解決だった根本原因を特定</b>"), itemStyle);
GUILayout.Label(L("• Unity Editor on Windows assigns itself a Win32 Job Object with KILL_ON_JOB_CLOSE", "• Unity Editor (Windows) は自身を Win32 Job Object に登録し KILL_ON_JOB_CLOSE フラグで管理している"), itemStyle);
GUILayout.Label(L("• Process.Start children inherit the Job and die on assembly reload / PlayMode transitions", "• Process.Start で起動した子プロセスはこの Job を継承し、アセンブリリロード / PlayMode 遷移で殺される"), itemStyle);
GUILayout.Label(L("• This is why the v1.2.10 → v1.2.11 internal→external rewrite did not fix it (node.exe was still in Unity's Job)", "• v1.2.10 → v1.2.11 で内部 C# → 外部 Node.js に切り出しても改善しなかったのはこのため (node.exe も Unity の Job 内にいた)"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: CreateProcessW with CREATE_BREAKAWAY_FROM_JOB</b>", "<b>★ 修正: CreateProcessW + CREATE_BREAKAWAY_FROM_JOB</b>"), itemStyle);
GUILayout.Label(L("• On Windows, node.exe is now spawned via P/Invoke CreateProcessW with BREAKAWAY_FROM_JOB | DETACHED_PROCESS | NEW_PROCESS_GROUP", "• Windows では P/Invoke で CreateProcessW を直接呼び、BREAKAWAY_FROM_JOB | DETACHED_PROCESS | NEW_PROCESS_GROUP を立てて spawn"), itemStyle);
GUILayout.Label(L("• The spawned node.exe now runs fully independent of Unity's Job Object", "• 起動した node.exe は Unity の Job から完全に独立"), itemStyle);
GUILayout.Label(L("• Same technique used by Unity's own Burst Compiler (BclApp.cs)", "• Unity 公式の Burst Compiler (BclApp.cs) と同じ技法"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: PID Recovery After Domain Reload</b>", "<b>★ 修正: ドメインリロード後の PID 復元</b>"), itemStyle);
GUILayout.Label(L("• Node PID stored in SessionState + EditorPrefs", "• Node PID を SessionState + EditorPrefs に保存"), itemStyle);
GUILayout.Label(L("• [InitializeOnLoadMethod] re-attaches by PID after reload and reconnects WebSocket only", "• [InitializeOnLoadMethod] でリロード後に PID から再接続。WebSocket だけ繋ぎ直し"), itemStyle);
GUILayout.Label(L("• No more 'Connect Unity Only' button required after script edits", "• スクリプト編集後に 'Connect Unity Only' を押す必要が無くなる"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: Parent-PID Watchdog (orphan guard)</b>", "<b>★ 修正: 親 PID watchdog (孤児防止)</b>"), itemStyle);
GUILayout.Label(L("• http-server.js now self-terminates 5s after Unity dies", "• http-server.js は Unity 死亡後 5秒以内に self-exit"), itemStyle);
GUILayout.Label(L("• Prevents zombie node.exe even when BREAKAWAY succeeds", "• BREAKAWAY 成功時でも node.exe ゾンビ化を防止"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Change: Detached log file</b>", "<b>★ 変更: detached モードのログファイル化</b>"), itemStyle);
GUILayout.Label(L("• DETACHED_PROCESS breaks stdout pipes, so node now writes to MCPServer/logs/http-server.log", "• DETACHED_PROCESS では stdout パイプが使えないため node 側でログファイル直書き (MCPServer/logs/http-server.log)"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ macOS / Linux behaviour unchanged</b>", "<b>★ macOS / Linux は従来通り</b>"), itemStyle);
GUILayout.Label(L("• No Job-Object-equivalent cascade-kill on these platforms — legacy Process.Start path retained", "• Job Object 相当の仕組みが無いため、従来の Process.Start 経路を維持"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.20
GUILayout.Label(L("v1.2.20 - Async Crash Fix & Log Cleanup", "v1.2.20 - 非同期クラッシュ修正 & ログ整理"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Async Thread Crash on Disconnect (ESC-0025)</b>", "<b>★ 修正: 切断時の非同期スレッドクラッシュ (ESC-0025)</b>"), itemStyle);
GUILayout.Label(L("• OnConnectionLost no longer crashes when called from non-main threads", "• OnConnectionLost がメインスレッド外から呼ばれてもクラッシュしない"), itemStyle);
GUILayout.Label(L("• Introduced ThreadSafeTime() using Stopwatch for async-safe timing", "• Stopwatch ベースの ThreadSafeTime() を導入し非同期安全な時刻取得に"), itemStyle);
GUILayout.Label(L("• Tool execution self-recovers without Unity restart", "• Unity を再起動せずにツール実行が自己復旧"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Change: Verbose Log Toggle</b>", "<b>★ 変更: 詳細ログのトグル</b>"), itemStyle);
GUILayout.Label(L("• Setup Window > HTTP Server > 'Verbose Logs' toggle", "• Setup Window > HTTP Server > 'Verbose Logs' で切り替え"), itemStyle);
GUILayout.Label(L("• Errors are always logged regardless of toggle", "• Error は常に表示される"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Change: Smaller Setup Window Min Size</b>", "<b>★ 変更: Setup Window の最小サイズ縮小</b>"), itemStyle);
GUILayout.Label(L("• 800x800 → 480x480, fits on small laptops and dockable", "• 800x800 → 480x480、小型ラップトップやドッキングに対応"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Safeguard: Auto-Update Validation</b>", "<b>★ セーフガード: 自動アップデート検証</b>"), itemStyle);
GUILayout.Label(L("• File size and marker checks prevent partial-archive overwrites", "• ファイルサイズとマーカー検証で破損アーカイブによる上書きを防止"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: Main Window Repaint Recursion</b>", "<b>★ 修正: メインウィンドウ再描画の無限再帰</b>"), itemStyle);
GUILayout.Label(L("• ThrottledRepaint() no longer recurses into itself", "• ThrottledRepaint() が自分自身を呼び続ける問題を修正"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.19
GUILayout.Label(L("v1.2.19 - Windows Stability (Community Contribution)", "v1.2.19 - Windows安定性 (コミュニティ貢献)"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Windows HTTP WebSocket Stability</b>", "<b>★ 修正: Windows HTTP WebSocket安定性</b>"), itemStyle);
GUILayout.Label(L("• Fixed HTTP server tab becoming unresponsive on Windows", "• WindowsでHTTPサーバータブが無反応になる問題を修正"), itemStyle);
GUILayout.Label(L("• Added reentrancy guard to prevent concurrent WebSocket connections", "• 同時WebSocket接続を防ぐ再入ガードを追加"), itemStyle);
GUILayout.Label(L("• Connect timeout (5s) prevents indefinite hang", "• 接続タイムアウト(5秒)で無限ハングを防止"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: MCP Reconnect Storm Prevention</b>", "<b>★ 修正: MCPリコネクト嵐の防止</b>"), itemStyle);
GUILayout.Label(L("• Added 10-second minimum interval between reconnect attempts", "• リコネクト試行間に最低10秒のインターバル追加"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ Fix: Setup Window UI Freeze</b>", "<b>★ 修正: Setup Window UIフリーズ</b>"), itemStyle);
GUILayout.Label(L("• Port check moved to background thread", "• ポートチェックをバックグラウンドスレッドに移動"), itemStyle);
GUILayout.Label(L("• Main Window repaint throttled to 10Hz", "• メインウィンドウの再描画を10Hzに制限"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ New: MCP Server Start/Stop Menu</b>", "<b>★ 新機能: MCP Server Start/Stopメニュー</b>"), itemStyle);
GUILayout.Label(L("• Tools > Synaptic Pro > MCP Server: Start/Stop", "• Tools > Synaptic Pro > MCP Server: Start/Stop"), itemStyle);
GUILayout.Space(5);
var creditStyle = new GUIStyle(itemStyle) { fontStyle = FontStyle.Italic };
GUILayout.Label(L("Special thanks to OverlordMethuselah777 for contributing these fixes!", "OverlordMethuselah777氏の修正貢献に感謝します!"), creditStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.18
GUILayout.Label(L("v1.2.18 - Auto-Update & WebSocket Stability", "v1.2.18 - 自動アップデート・WebSocket安定性"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ New: Auto-Update System</b>", "<b>★ 新機能: 自動アップデート</b>"), itemStyle);
GUILayout.Label(L("• Update check on Unity startup (once per day)", "• Unity起動時にアップデート確認(1日1回)"), itemStyle);
GUILayout.Label(L("• One-click update from Setup Window banner", "• Setup Windowのバナーからワンクリック更新"), itemStyle);
GUILayout.Label(L("<b>★ Fix: WebSocket Connection Stability</b>", "<b>★ 修正: WebSocket接続安定性</b>"), itemStyle);
GUILayout.Label(L("• Added ping/pong heartbeat every 30 seconds", "• 30秒ごとのping/pongハートビート追加"), itemStyle);
GUILayout.Label(L("• Reconnect attempts increased to 30 (2s intervals)", "• 再接続試行を30回に増加(2秒間隔)"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.17
GUILayout.Label(L("v1.2.17 - Editor Performance Fix", "v1.2.17 - エディタパフォーマンス修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: HTTP Server Tab Performance</b>", "<b>★ 修正: HTTP Serverタブのパフォーマンス</b>"), itemStyle);
GUILayout.Label(L("• Fixed editor slowdown when HTTP Server tab is open but server not started", "• HTTPサーバー未起動時にHTTP Serverタブを開くとエディタが重くなる問題を修正"), itemStyle);
GUILayout.Label(L("• Server status check now runs every 5 seconds instead of every frame", "• サーバーステータス確認を毎フレームから5秒間隔に変更"), itemStyle);
GUILayout.Label(L("• Added UTF-8 encoding for Node.js process output", "• Node.jsプロセス出力のUTF-8エンコーディングを追加"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.16
GUILayout.Label(L("v1.2.16 - Unity 6 GUILayout Fix", "v1.2.16 - Unity 6 GUILayout修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Unity 6 GUILayout & Auto-Start</b>", "<b>★ 修正: Unity 6 GUILayout・Auto-Start</b>"), itemStyle);
GUILayout.Label(L("• Fixed GUILayout Begin/End mismatch error in DrawHeader and HTTP Server UI", "• DrawHeaderとHTTP Server UIのGUILayout Begin/End不一致エラーを修正"), itemStyle);
GUILayout.Label(L("• Fixed Auto-Start not working after domain reload", "• ドメインリロード後にAuto-Startが動作しない問題を修正"), itemStyle);
GUILayout.Label(L("• Auto-Start now detects existing server and reconnects instead of restarting", "• Auto-Start時に既存サーバーを検出し再接続するように改善"), itemStyle);
GUILayout.Label(L("• Added UTF-8 encoding for Node.js process output", "• Node.jsプロセス出力のUTF-8エンコーディングを追加"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.15
GUILayout.Label(L("v1.2.15 - Setup Window Performance Fix", "v1.2.15 - Setup Windowパフォーマンス修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Setup Window Freeze on Large Projects</b>", "<b>★ 修正: 大規模プロジェクトでのSetup Windowフリーズ</b>"), itemStyle);
GUILayout.Label(L("• FindMCPServerPath now uses cached result instead of searching every call", "• FindMCPServerPathの結果をキャッシュし毎回の検索を排除"), itemStyle);
GUILayout.Label(L("• Eliminated recursive AllDirectories search that caused 'Hold on' dialog on large projects", "• 大規模プロジェクトで「Hold on」を引き起こしていた再帰的全ディレクトリ検索を廃止"), itemStyle);
GUILayout.Label(L("• PackageCache search limited to com.synaptic* packages only", "• PackageCache検索をcom.synaptic*パッケージのみに限定"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.14
GUILayout.Label(L("v1.2.14 - Windows Stability & Process Management Fix", "v1.2.14 - Windows安定性・プロセス管理修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Hold on Dialog & Process Cleanup</b>", "<b>★ 修正: Hold onダイアログ・プロセス管理</b>"), itemStyle);
GUILayout.Label(L("• Fixed 'Hold on' dialog caused by blocking CheckSetupStatus on main thread", "• メインスレッドをブロックするCheckSetupStatusによる「Hold on」を修正"), itemStyle);
GUILayout.Label(L("• Improved Windows command path detection (git/node/npm) - matching MCP config robustness", "• Windowsでのコマンドパス検出を強化(git/node/npm- MCP設定と同等の堅牢性"), itemStyle);
GUILayout.Label(L("• Node.js process now properly killed on Unity quit and domain reload", "• Unity終了時・ドメインリロード時にNode.jsプロセスを確実に停止"), itemStyle);
GUILayout.Label(L("• Auto-Start now kills stale processes before launching", "• Auto-Start時に残存プロセスを終了してから起動"), itemStyle);
GUILayout.Label(L("• Fixed MCP port conflict when multiple Claude Code sessions connect", "• 複数Claude Codeセッション接続時のMCPポート競合を修正"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.13
GUILayout.Label(L("v1.2.13 - Setup Window Stability Fix", "v1.2.13 - Setup Window安定性修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Setup Window Repaint Loop</b>", "<b>★ 修正: Setup Windowの再描画ループ</b>"), itemStyle);
GUILayout.Label(L("• Fixed 'Hold on' dialog appearing endlessly when switching tabs during MCP connection", "• MCP接続中にタブを切り替えると「Hold on」ダイアログが無限に出る問題を修正"), itemStyle);
GUILayout.Label(L("• Repaint animation now only runs on AI Connection tab", "• 再描画アニメーションをAI Connectionタブのみに限定"), itemStyle);
GUILayout.Label(L("• Reduced console debug log output", "• コンソールのデバッグログ出力を削減"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.12
GUILayout.Label(L("v1.2.12 - Windows HTTP Server Fix", "v1.2.12 - Windows HTTPサーバー修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Fix: Windows HTTP Server Launch</b>", "<b>★ 修正: Windows HTTPサーバー起動</b>"), itemStyle);
GUILayout.Label(L("• Fixed HTTP Server failing to start on Windows when project path contains spaces", "• プロジェクトパスにスペースが含まれる場合にHTTPサーバーが起動しない問題を修正"), itemStyle);
GUILayout.Label(L("• Windows now launches Node.js via cmd.exe for reliable path handling", "• Windowsではcmd.exe経由でNode.jsを起動し、パス処理の信頼性を向上"), itemStyle);
GUILayout.Label(L("• Improved WebSocket connection retry logic (3 attempts with 2s intervals)", "• WebSocket接続のリトライロジックを改善(2秒間隔で3回リトライ)"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.11
GUILayout.Label(L("v1.2.11 - Claude Desktop CWD Fix", "v1.2.11 - Claude Desktop CWD修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Critical Fix: Claude Desktop MCP Configuration</b>", "<b>★ 重要修正: Claude Desktop MCP設定</b>"), itemStyle);
GUILayout.Label(L("• Added missing 'cwd' to Claude Desktop config (was only set for VS Code/Gemini/Cursor)", "• Claude Desktop設定に欠けていた'cwd'を追加(VS Code/Gemini/Cursor用には設定済みだった)"), itemStyle);
GUILayout.Label(L("• Fixes connection timeout on non-standard paths (spaces, Japanese, external drives)", "• 非標準パス(スペース、日本語、外部ドライブ)での接続タイムアウトを修正"), itemStyle);
GUILayout.Label(L("• Node.js now runs from MCPServer directory for reliable require() resolution", "• Node.jsがMCPServerディレクトリから実行され、require()の解決が確実に"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.10
GUILayout.Label(L("v1.2.10 - MCP Process Fix & Resources", "v1.2.10 - MCPプロセス修正 & リソース"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Critical Fix: MCP Dual-Process Startup Bug</b>", "<b>★ 重要修正: MCP二重プロセス起動バグ</b>"), itemStyle);
GUILayout.Label(L("• Fixed Claude Code starting 2 MCP processes simultaneously", "• Claude Codeが2つのMCPプロセスを同時起動する問題を修正"), itemStyle);
GUILayout.Label(L("• Added retry logic with WebSocket shutdown handover", "• WebSocketシャットダウンハンドオーバー付きリトライロジック追加"), itemStyle);
GUILayout.Label(L("• Graceful process takeover for reliable connection", "• 確実な接続のためのグレースフルプロセステイクオーバー"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ New: MCP Resources Protocol</b>", "<b>★ 新機能: MCPリソースプロトコル</b>"), itemStyle);
GUILayout.Label(L("• synaptic://tools/reference - Compact tools reference", "• synaptic://tools/reference - コンパクトツールリファレンス"), itemStyle);
GUILayout.Label(L("• synaptic://tools/reference/full - Full with inputSchema", "• synaptic://tools/reference/full - inputSchema付きフル版"), itemStyle);
GUILayout.Label(L("• GET /resources, /resources/read HTTP endpoints", "• GET /resources, /resources/read HTTPエンドポイント"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.9
GUILayout.Label(L("v1.2.9 - TcpListener HTTP Server", "v1.2.9 - TcpListener HTTPサーバー"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Critical Fix: Windows Port Reuse (Domain Reload)</b>", "<b>★ 重要修正: Windowsポート再利用 (ドメインリロード)</b>"), itemStyle);
GUILayout.Label(L("• Replaced HttpListener with TcpListener", "• HttpListenerをTcpListenerに置換"), itemStyle);
GUILayout.Label(L("• Added SO_REUSEADDR socket option", "• SO_REUSEADDRソケットオプション追加"), itemStyle);
GUILayout.Label(L("• Bypasses HTTP.sys kernel driver on Windows", "• WindowsでHTTP.sysカーネルドライバをバイパス"), itemStyle);
GUILayout.Label(L("• Reliable port recovery after script recompilation", "• スクリプトリコンパイル後のポート復帰が確実に"), itemStyle);
GUILayout.Label(L("• Removed dangerous ForceKillPortProcess code", "• 危険なForceKillPortProcessコードを削除"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.8
GUILayout.Label(L("v1.2.8 - Tool Search & Material Fix", "v1.2.8 - ツール検索 & マテリアル修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ New: Tool Search Endpoint</b>", "<b>★ 新機能: ツール検索エンドポイント</b>"), itemStyle);
GUILayout.Label(L("• GET /tools/search?q=keyword - Search by name, title, description", "• GET /tools/search?q=keyword - 名前・タイトル・説明で検索"), itemStyle);
GUILayout.Label(L("• Optional category filter and result limit", "• カテゴリフィルタと結果数制限をサポート"), itemStyle);
GUILayout.Label(L("• Relevance scoring for better results", "• 関連性スコアリングで最適な結果を表示"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ New: MCP search_tools Meta-Tool</b>", "<b>★ 新機能: MCP search_tools メタツール</b>"), itemStyle);
GUILayout.Label(L("• Search tools without knowing exact category", "• カテゴリ不明でもキーワード検索可能"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>Improved: HTTP Server Port Recovery</b>", "<b>改善: HTTPサーバーポート復帰</b>"), itemStyle);
GUILayout.Label(L("• Force kill process blocking port on startup", "• 起動時にポートをブロックするプロセスを強制終了"), itemStyle);
GUILayout.Label(L("• No more 'port already in use' errors", "• 「ポート使用中」エラーが発生しなくなりました"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>Fix: MeshRenderer Material Assignment</b>", "<b>修正: MeshRendererマテリアル設定</b>"), itemStyle);
GUILayout.Label(L("• Fixed circular reference error (Color.linear)", "• 循環参照エラー(Color.linear)を修正"), itemStyle);
GUILayout.Label(L("• Material/Texture now serialize correctly", "• Material/Textureが正常にシリアライズ"), itemStyle);
GUILayout.Space(5);
GUILayout.Label(L("<b>★ New: Console Log Filtering</b>", "<b>★ 新機能: コンソールログフィルタリング</b>"), itemStyle);
GUILayout.Label(L("• excludeSynaptic: Auto-filter internal logs (default: true)", "• excludeSynaptic: 内部ログ自動除外 (デフォルト: true)"), itemStyle);
GUILayout.Label(L("• filter/exclude: Custom include/exclude patterns", "• filter/exclude: カスタムパターンで絞り込み"), itemStyle);
GUILayout.Label(L("• groupByMessage: Deduplicate with count", "• groupByMessage: 重複ログを件数表示でまとめる"), itemStyle);
GUILayout.Label(L("• Reduces token usage significantly", "• トークン消費を大幅削減"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.7
GUILayout.Label(L("v1.2.7 - Windows Domain Reload Fix", "v1.2.7 - Windowsドメインリロード修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ Critical Fix: Domain Reload Recovery (Windows)</b>", "<b>★ 重要修正: ドメインリロード復帰 (Windows)</b>"), itemStyle);
GUILayout.Label(L("• Fixed HTTP server not recovering after script recompilation", "• スクリプトリコンパイル後にHTTPサーバーが復帰しない問題を修正"), itemStyle);
GUILayout.Label(L("• Added ForceReleasePort() before assembly reload", "• アセンブリリロード前にForceReleasePort()を追加"), itemStyle);
GUILayout.Label(L("• GC.Collect() ensures port is properly released", "• GC.Collect()でポートを確実に解放"), itemStyle);
GUILayout.Label(L("• Thread.Join(500) for graceful thread termination", "• Thread.Join(500)でスレッドを適切に終了"), itemStyle);
GUILayout.Label(L("• Increased retry attempts (15x) and delay (1s)", "• リトライ回数(15回)と間隔(1秒)を増加"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.6
GUILayout.Label(L("v1.2.6 - HTTP Server Stability", "v1.2.6 - HTTPサーバー安定性向上"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>Port Release & Binding Improvements</b>", "<b>ポート解放・バインディング改善</b>"), itemStyle);
GUILayout.Label(L("• Added Abort() on Stop for immediate port release", "• Stop時にAbort()追加で即座にポート解放"), itemStyle);
GUILayout.Label(L("• Added retry logic for TIME_WAIT state (Windows)", "• TIME_WAIT状態へのリトライ処理追加(Windows)"), itemStyle);
GUILayout.Label(L("• Up to 5 retries with 500ms delay", "• 最大5回リトライ、500ms間隔"), itemStyle);
GUILayout.Label(L("• Prevents 'port already in use' errors", "• 「ポートが使用中」エラーを防止"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.5
GUILayout.Label(L("v1.2.5 - VFX Graph Fixes & HTTP Prompt API", "v1.2.5 - VFX Graph修正 & HTTPプロンプトAPI"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ New: HTTP Prompt Endpoint</b>", "<b>★ 新機能: HTTPプロンプトエンドポイント</b>"), itemStyle);
GUILayout.Label(L("• GET /prompt - Fetch AI control prompt directly", "• GET /prompt - AIプロンプトを直接取得可能"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>★ New: Test Runner Auto-Execution</b>", "<b>★ 新機能: テストランナー自動実行</b>"), itemStyle);
GUILayout.Label(L("• unity_run_tests now executes tests automatically", "• unity_run_testsがテストを自動実行"), itemStyle);
GUILayout.Label(L("• operation: run, results, list", "• operation: run, results, list"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>VFX Graph Fixes</b>", "<b>VFX Graph修正</b>"), itemStyle);
GUILayout.Label(L("• Fixed add_context, add_parameter, add_block", "• add_context, add_parameter, add_blockを修正"), itemStyle);
GUILayout.Label(L("• Fixed set_attribute 'Ambiguous match' error", "• set_attributeの'Ambiguous match'エラー修正"), itemStyle);
GUILayout.Label(L("• Improved reflection handling for Unity 2022.3+", "• Unity 2022.3+のリフレクション処理改善"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>Other Fixes</b>", "<b>その他の修正</b>"), itemStyle);
GUILayout.Label(L("• HTTP server: Play mode transition stability", "• HTTPサーバー: Playモード切替時の安定性向上"), itemStyle);
GUILayout.Label(L("• Animator window now updates after script changes", "• スクリプト変更後にAnimatorウィンドウが更新"), itemStyle);
GUILayout.Label(L("• Reduced MCP connection log noise", "• MCP接続ログのノイズ削減"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.4
GUILayout.Label(L("v1.2.4 - Dynamic Meta-Tools & Critical Fix", "v1.2.4 - 動的メタツール & 重要修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ New: Dynamic Meta-Tools</b>", "<b>★ 新機能: 動的メタツール</b>"), itemStyle);
GUILayout.Label(L("• unity_dynamic_inspect - Inspect any component", "• unity_dynamic_inspect - 任意コンポーネントの検査"), itemStyle);
GUILayout.Label(L("• unity_dynamic_modify - Modify any property", "• unity_dynamic_modify - 任意プロパティの変更"), itemStyle);
GUILayout.Label(L("• unity_dynamic_create - Create objects/prefabs", "• unity_dynamic_create - オブジェクト/プレハブ作成"), itemStyle);
GUILayout.Label(L("• Available as inspect/modify/create in SuperSave", "• SuperSaveでinspect/modify/createとして利用可"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>Critical Fix</b>", "<b>重要な修正</b>"), itemStyle);
GUILayout.Label(L("• Fixed prefabs contaminating scene analysis", "• シーン分析にプレハブが混入する問題を修正"), itemStyle);
GUILayout.Label(L("• analyze_draw_calls now returns only scene objects", "• analyze_draw_callsがシーン内オブジェクトのみ返す"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>Other Fixes</b>", "<b>その他の修正</b>"), itemStyle);
GUILayout.Label(L("• HTTP server port cleanup on recompile", "• HTTPサーバーのポート解放問題修正"), itemStyle);
GUILayout.Label(L("• Script creation path parameter added", "• スクリプト作成のpath引数追加"), itemStyle);
GUILayout.Label(L("• Windows Node.js path detection improved", "• Windows Node.jsパス検出改善"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.3
GUILayout.Label(L("v1.2.3 - HTTP API Fix", "v1.2.3 - HTTP API修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>Fixed</b>", "<b>修正</b>"), itemStyle);
GUILayout.Label(L("• HTTP API: All tools now work via /execute, /batch", "• HTTP API: 全ツールが/execute, /batchで動作"), itemStyle);
GUILayout.Label(L("• Fixed 'Unknown operation' error for unmapped tools", "• マッピングなしツールの'Unknown operation'エラー修正"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.2
GUILayout.Label(L("v1.2.2 - SuperSave Fixes", "v1.2.2 - SuperSave修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>Fixed</b>", "<b>修正</b>"), itemStyle);
GUILayout.Label(L("• SuperSave execute tool now works correctly", "• SuperSaveのexecuteツールが正常に動作"), itemStyle);
GUILayout.Label(L("• All MCP clients use selected server mode", "• 全MCPクライアントで選択モードを使用"), itemStyle);
GUILayout.Label(L("• Component details for all types (not null)", "• 全コンポーネントの詳細情報を取得可能"), itemStyle);
GUILayout.Label(L("• Filter aliases: tag, layer, name accepted", "• フィルタ別名: tag, layer, name対応"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.1
GUILayout.Label(L("v1.2.1 - Hotfix", "v1.2.1 - 緊急修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>Fixed</b>", "<b>修正</b>"), itemStyle);
GUILayout.Label(L("• SuperSave Mode: Added shutdown handlers", "• SuperSave: シャットダウン処理追加"), itemStyle);
GUILayout.Label(L("• Proper MCP client cleanup on exit", "• MCP終了時の適切なクリーンアップ"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.2.0
GUILayout.Label(L("v1.2.0 - Token SuperSave Mode", "v1.2.0 - トークン SuperSave モード"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("<b>★ New: Token SuperSave Mode [Recommended]</b>", "<b>★ 新機能: Token SuperSave モード [推奨]</b>"), itemStyle);
GUILayout.Label(L("• 99% context reduction with only 3 meta-tools", "• 3つのメタツールで99%のコンテキスト削減"), itemStyle);
GUILayout.Label(L("• list_categories() - Discover tool categories", "• list_categories() - カテゴリ一覧"), itemStyle);
GUILayout.Label(L("• list_tools(category) - See tools & parameters", "• list_tools(category) - ツール詳細"), itemStyle);
GUILayout.Label(L("• execute(tool, params) - Run any of 350+ tools", "• execute(tool, params) - 350+ツール実行"), itemStyle);
GUILayout.Label(L("• Works with all MCP clients", "• 全MCPクライアント対応"), itemStyle);
GUILayout.Label(L("• Best for long AI sessions", "• 長いAIセッションに最適"), itemStyle);
GUILayout.Space(10);
GUILayout.Label(L("<b>Improvements</b>", "<b>改善</b>"), itemStyle);
GUILayout.Label(L("• Setup window redesigned with mode selection", "• セットアップ画面をモード選択式に刷新"), itemStyle);
GUILayout.Label(L("• SuperSave Mode set as default", "• SuperSaveモードをデフォルトに"), itemStyle);
GUILayout.Label(L("• Better error messages with suggestions", "• エラーメッセージに提案を追加"), itemStyle);
GUILayout.Label(L("• Tool registry loaded from JSON dynamically", "• ツール定義をJSONから動的読み込み"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.1.9
GUILayout.Label(L("v1.1.9 - Stability Fixes", "v1.1.9 - 安定性修正"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("• Batch tool format conversion fix", "• バッチツールのフォーマット変換修正"), itemStyle);
GUILayout.Label(L("• MCP stdio protocol stability (JSON-RPC)", "• MCP stdio プロトコル安定化"), itemStyle);
GUILayout.Label(L("• HTTP server localhost binding fix", "• HTTPサーバーのlocalhost接続修正"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// v1.1.8
GUILayout.Label(L("v1.1.8 - Sphere Skybox", "v1.1.8 - 球体スカイボックス"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label(L("• Sphere Skybox: Create skybox from any photo", "• 球体スカイボックス: 写真からスカイボックス生成"), itemStyle);
GUILayout.Label(L("• Multi-pipeline shader support", "• マルチパイプラインシェーダー対応"), itemStyle);
GUILayout.Label(L("• MCP server renamed to unity-synaptic", "• MCPサーバー名をunity-synapticに変更"), itemStyle);
EditorGUILayout.EndVertical();
GUILayout.Space(15);
// Links
GUILayout.Label(L("Links", "リンク"), sectionStyle);
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(L("Documentation", "ドキュメント"), GUILayout.Height(25)))
{
Application.OpenURL("https://synaptic-ai.net/docs");
}
if (GUILayout.Button("Discord", GUILayout.Height(25)))
{
Application.OpenURL("https://discord.gg/MXwHCVWmPe");
}
if (GUILayout.Button(L("Website", "ウェブサイト"), GUILayout.Height(25)))
{
Application.OpenURL("https://synaptic-ai.net");
}
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// Reset the "don't show" preference (for testing)
/// </summary>
[MenuItem("Tools/Synaptic Pro/Reset Changelog Preference", false, 101)]
public static void ResetPreference()
{
EditorPrefs.DeleteKey(PREF_KEY_LAST_VERSION);
EditorPrefs.DeleteKey(PREF_KEY_DONT_SHOW);
SynLog.Info("[Synaptic] Changelog preference reset. Will show on next startup.");
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 58854a77bb2b1482c96c04eb6ab06187
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusChangelogWindow.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 7c8f3e5a9b2d4f1c8e6a3d5b7f9e1c4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusCinemachineHelper.cs
uploadId: 920982
@@ -1,371 +0,0 @@
using System;
using System.Diagnostics;
using UnityEngine;
using SynapticAIPro;
using UnityEditor;
namespace SynapticPro
{
/// <summary>
/// Cleanup MCP server on Unity exit or play mode change
/// </summary>
[InitializeOnLoad]
public static class NexusCleanupHandler
{
static NexusCleanupHandler()
{
// [ProjectLazarus patch — REVERTED 2026-04-23]
// We tried adding `afterAssemblyReload += Connect()` here to skip the up-to-10s
// wait for NexusEditorMCPService.Update's poll. But it raced with
// NexusSetupWindow.OnEnable's existing auto-start Connect call, and the resulting
// double-Connect created an infinite WS connect→die→reconnect loop. Reverted.
// Original symptom (WS reconnect lag after heavy ops) is acceptable; Tools/synaptic-server.cmd
// is the manual escape hatch when needed.
// Event on Unity editor exit
EditorApplication.wantsToQuit += OnEditorQuitting;
// Event on play mode change
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
// On domain reload (after script compilation, etc.)
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
// Cleanup existing MCP server processes on startup
CleanupExistingProcesses();
}
private static bool OnEditorQuitting()
{
SynLog.Info("[Synaptic] Unity exit detected - Cleaning up MCP server");
// Ensure MCP server is stopped
var stopTask = NexusMCPSetupManager.Instance?.StopMCPServer();
if (stopTask != null)
{
// Wait synchronously for async operation (max 5 seconds)
if (stopTask.Wait(5000))
{
SynLog.Info("[Synaptic] MCP server stopped successfully");
}
else
{
SynLog.Warn("[Synaptic] MCP server stop timed out");
}
}
CleanupMCPServer();
return true; // Continue Unity exit
}
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode)
{
SynLog.Info($"[Synaptic] Play mode change detected ({state}) - Checking MCP server");
// Cleanup as needed on play mode change
// Usually maintain server, but stop if there are issues
}
}
private static void OnBeforeAssemblyReload()
{
SynLog.Info("[Synaptic] Before assembly reload - Saving MCP server state");
// Handle state saving before assembly reload if needed
}
/// <summary>
/// Cleanup existing MCP server processes on startup
/// </summary>
private static void CleanupExistingProcesses()
{
try
{
// In new architecture, cleanup on Unity startup is not needed
// Claude Desktop manages MCP server
SynLog.Info("[Synaptic] Unity is now a MCP client - skipping server cleanup");
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"[Synaptic] Error in cleanup: {e.Message}");
}
}
/// <summary>
/// Check if port is in use
/// </summary>
private static bool CheckPortInUse(int port)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/usr/sbin/lsof",
Arguments = $"-i :{port}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return !string.IsNullOrEmpty(output);
}
catch
{
// If lsof fails, try another method
return false;
}
}
/// <summary>
/// Cleanup MCP server
/// </summary>
private static void CleanupMCPServer()
{
try
{
KillNodeProcesses();
SynLog.Info("[Synaptic] MCP server cleaned up");
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"[Synaptic] MCP server cleanup error: {e.Message}");
}
}
/// <summary>
/// Terminate Node.js processes
/// </summary>
private static void KillNodeProcesses()
{
try
{
var killProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/usr/bin/pkill",
Arguments = "-f \"node.*index.js\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
killProcess.Start();
killProcess.WaitForExit();
// Wait a bit for process to fully terminate
System.Threading.Thread.Sleep(500);
}
catch (Exception e)
{
SynLog.Warn($"[Synaptic] Warning during process termination: {e.Message}");
}
}
/// <summary>
/// Search for available port
/// </summary>
private static int FindAvailablePort(int startPort, int endPort)
{
for (int port = startPort; port <= endPort; port++)
{
if (!CheckPortInUse(port))
{
return port;
}
}
return -1; // Not found
}
/// <summary>
/// Update MCP client ports (HTTP + WebSocket)
/// </summary>
private static void UpdateMCPClientPorts(int httpPort, int wsPort)
{
try
{
#if UNITY_EDITOR
// Update WebSocket client port (Editor only)
try
{
var webSocketClientType = System.Type.GetType("NexusAIConnect.NexusWebSocketClient");
if (webSocketClientType != null)
{
var instanceProperty = webSocketClientType.GetProperty("Instance", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
var instance = instanceProperty?.GetValue(null);
if (instance != null)
{
var setServerUrlMethod = webSocketClientType.GetMethod("SetServerUrl");
setServerUrlMethod?.Invoke(instance, new object[] { $"ws://localhost:{wsPort}" });
SynLog.Info($"[Synaptic] WebSocket client port updated to {wsPort}");
}
}
}
catch (System.Exception ex)
{
SynLog.Warn($"[Synaptic] Failed to update WebSocket client: {ex.Message}");
}
#endif
// Update Editor MCP Service port
try
{
var editorMCPServiceType = System.Type.GetType("NexusAIConnect.NexusEditorMCPService");
if (editorMCPServiceType != null)
{
var setServerUrlMethod = editorMCPServiceType.GetMethod("SetServerUrl", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
setServerUrlMethod?.Invoke(null, new object[] { $"ws://localhost:{wsPort}" });
SynLog.Info($"[Synaptic] Editor MCP Service port updated to {wsPort}");
}
}
catch (System.Exception ex)
{
SynLog.Warn($"[Synaptic] Failed to update Editor MCP Service: {ex.Message}");
}
// Update MCP client port (for Runtime)
var mcpClient = NexusMCPClient.Instance;
if (mcpClient != null)
{
mcpClient.SetServerUrl($"ws://localhost:{wsPort}");
SynLog.Info($"[Synaptic] MCP client port updated to {wsPort}");
}
// Update Claude Desktop configuration
if (wsPort != 8090 || httpPort != 3000)
{
UpdateClaudeDesktopConfig(httpPort, wsPort);
}
// HTTP port settings (for future expansion)
if (httpPort != 3000)
{
SynLog.Info($"[Synaptic] HTTP port changed to {httpPort} (will apply on MCP server startup)");
// TODO: Update MCP server config file or environment variables
System.Environment.SetEnvironmentVariable("NEXUS_HTTP_PORT", httpPort.ToString());
}
}
catch (System.Exception e)
{
UnityEngine.Debug.LogError($"[Synaptic] Error during port update: {e.Message}");
}
}
/// <summary>
/// Dynamically update Claude Desktop config file ports (HTTP + WebSocket)
/// </summary>
private static void UpdateClaudeDesktopConfig(int newHttpPort, int newWsPort)
{
try
{
// Detect platform-specific Claude Desktop config path
string configPath;
if (UnityEngine.Application.platform == UnityEngine.RuntimePlatform.OSXEditor)
{
configPath = System.IO.Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
"Library", "Application Support", "Claude", "claude_desktop_config.json"
);
}
else if (UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor)
{
configPath = System.IO.Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
"Claude", "claude_desktop_config.json"
);
}
else
{
SynLog.Warn("[Synaptic] Unsupported platform for Claude Desktop config update");
return;
}
if (!System.IO.File.Exists(configPath))
{
SynLog.Warn($"[Synaptic] Claude Desktop config file not found: {configPath}");
return;
}
// Load config file
string configContent = System.IO.File.ReadAllText(configPath);
bool updated = false;
// Update WebSocket port (supports multiple patterns)
string newWsPattern = $"ws://localhost:{newWsPort}";
string[] oldWsPatterns = {
"ws://localhost:8090",
"ws://localhost:8081",
"ws://localhost:8082",
"ws://localhost:8083",
"ws://localhost:8084"
};
foreach (string oldWsPattern in oldWsPatterns)
{
if (configContent.Contains(oldWsPattern) && oldWsPattern != newWsPattern)
{
configContent = configContent.Replace(oldWsPattern, newWsPattern);
SynLog.Info($"[Synaptic] WebSocket port updated: {oldWsPattern} → {newWsPattern}");
updated = true;
}
}
// Update HTTP port
string oldHttpPattern = "http://localhost:3000";
string newHttpPattern = $"http://localhost:{newHttpPort}";
if (configContent.Contains(oldHttpPattern))
{
configContent = configContent.Replace(oldHttpPattern, newHttpPattern);
SynLog.Info($"[Synaptic] HTTP port updated: {oldHttpPattern} → {newHttpPattern}");
updated = true;
}
// Update port number only ("port": 3000 → "port": 3001)
string oldPortPattern = "\"port\": 3000";
string newPortPattern = $"\"port\": {newHttpPort}";
if (configContent.Contains(oldPortPattern))
{
configContent = configContent.Replace(oldPortPattern, newPortPattern);
SynLog.Info($"[Synaptic] Port setting updated: {oldPortPattern} → {newPortPattern}");
updated = true;
}
if (updated)
{
// Create backup
string backupPath = configPath + $".backup_{System.DateTime.Now:yyyyMMdd_HHmmss}";
System.IO.File.Copy(configPath, backupPath);
// Write new configuration
System.IO.File.WriteAllText(configPath, configContent);
SynLog.Info($"[Synaptic] Claude Desktop configuration updated");
SynLog.Info($"[Synaptic] Backup created: {backupPath}");
SynLog.Info($"[Synaptic] 🔄 Please restart Claude Desktop");
}
else
{
SynLog.Warn($"[Synaptic] No update targets found in config file");
}
}
catch (System.Exception e)
{
UnityEngine.Debug.LogError($"[Synaptic] Claude Desktop config update error: {e.Message}");
}
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 24f165b7a5bac4665ac1a31f7ed68729
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusCleanupHandler.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 2fc1cd5d4a34c4ba8a53030b8e71547d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusEditorMCPService.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 13df24e3cf3dc4e3493cdd3e012b4dc6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusExecutor.cs
uploadId: 920982
@@ -1,989 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Dynamic Meta-Tools for NexusUnityExecutor
/// Provides reflection-based inspection and modification of Unity components
/// </summary>
public partial class NexusUnityExecutor
{
#region Dynamic Meta-Tools
/// <summary>
/// Dynamically inspect any Unity object, component, scene, or project assets
/// </summary>
private string DynamicInspect(Dictionary<string, string> parameters)
{
try
{
var target = parameters.GetValueOrDefault("target", "gameobject").ToLower();
var name = parameters.GetValueOrDefault("name", "");
var componentType = parameters.GetValueOrDefault("component", "");
var path = parameters.GetValueOrDefault("path", "");
int.TryParse(parameters.GetValueOrDefault("depth", "2"), out int depth);
switch (target)
{
case "gameobject":
return InspectGameObject(name, depth);
case "component":
return InspectComponent(name, componentType, depth);
case "scene":
return InspectScene();
case "hierarchy":
return InspectHierarchy(depth);
case "prefabs":
return InspectPrefabs(path);
case "project":
return InspectProject(path);
default:
return JsonConvert.SerializeObject(new { error = $"Unknown inspect target: {target}" });
}
}
catch (Exception e)
{
return CreateErrorResponse("DynamicInspect", e, parameters);
}
}
private string InspectGameObject(string name, int depth)
{
if (string.IsNullOrEmpty(name))
{
// List all root GameObjects
var rootObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene()
.GetRootGameObjects()
.Select(go => new {
name = go.name,
active = go.activeSelf,
components = go.GetComponents<Component>().Select(c => c?.GetType().Name).Where(n => n != null).ToList(),
childCount = go.transform.childCount
}).ToList();
return JsonConvert.SerializeObject(new {
success = true,
message = $"Found {rootObjects.Count} root GameObjects",
gameObjects = rootObjects
});
}
var gameObject = GameObject.Find(name);
if (gameObject == null)
{
gameObject = FindGameObjectByPathDynamic(name);
}
if (gameObject == null)
{
return JsonConvert.SerializeObject(new { error = $"GameObject '{name}' not found" });
}
var components = new List<object>();
foreach (var comp in gameObject.GetComponents<Component>())
{
if (comp == null) continue;
components.Add(new {
type = comp.GetType().Name,
fullType = comp.GetType().FullName,
enabled = (comp is Behaviour b) ? b.enabled : true
});
}
var children = new List<object>();
if (depth > 0)
{
foreach (Transform child in gameObject.transform)
{
children.Add(new {
name = child.name,
active = child.gameObject.activeSelf,
componentCount = child.GetComponents<Component>().Length,
childCount = child.childCount
});
}
}
return JsonConvert.SerializeObject(new {
success = true,
gameObject = new {
name = gameObject.name,
active = gameObject.activeSelf,
layer = LayerMask.LayerToName(gameObject.layer),
tag = gameObject.tag,
isStatic = gameObject.isStatic,
transform = new {
position = new { x = gameObject.transform.position.x, y = gameObject.transform.position.y, z = gameObject.transform.position.z },
rotation = new { x = gameObject.transform.eulerAngles.x, y = gameObject.transform.eulerAngles.y, z = gameObject.transform.eulerAngles.z },
scale = new { x = gameObject.transform.localScale.x, y = gameObject.transform.localScale.y, z = gameObject.transform.localScale.z }
},
components = components,
children = children
}
});
}
private string InspectComponent(string gameObjectName, string componentType, int depth)
{
if (string.IsNullOrEmpty(gameObjectName))
{
return JsonConvert.SerializeObject(new { error = "GameObject name required" });
}
var gameObject = GameObject.Find(gameObjectName) ?? FindGameObjectByPathDynamic(gameObjectName);
if (gameObject == null)
{
return JsonConvert.SerializeObject(new { error = $"GameObject '{gameObjectName}' not found" });
}
Component component = null;
if (!string.IsNullOrEmpty(componentType))
{
component = gameObject.GetComponents<Component>()
.FirstOrDefault(c => c != null &&
(c.GetType().Name.Equals(componentType, StringComparison.OrdinalIgnoreCase) ||
c.GetType().Name.EndsWith(componentType, StringComparison.OrdinalIgnoreCase)));
}
if (component == null && !string.IsNullOrEmpty(componentType))
{
return JsonConvert.SerializeObject(new {
error = $"Component '{componentType}' not found on '{gameObjectName}'",
availableComponents = gameObject.GetComponents<Component>()
.Where(c => c != null)
.Select(c => c.GetType().Name)
.ToList()
});
}
// If no specific component, list all with their properties
if (component == null)
{
var allComponents = new List<object>();
foreach (var comp in gameObject.GetComponents<Component>())
{
if (comp == null) continue;
allComponents.Add(new {
type = comp.GetType().Name,
properties = GetSerializedPropertiesDynamic(comp, depth)
});
}
return JsonConvert.SerializeObject(new {
success = true,
gameObject = gameObjectName,
components = allComponents
});
}
// Inspect specific component
var properties = GetSerializedPropertiesDynamic(component, depth);
return JsonConvert.SerializeObject(new {
success = true,
gameObject = gameObjectName,
component = componentType,
type = component.GetType().FullName,
properties = properties
});
}
private List<object> GetSerializedPropertiesDynamic(Component component, int depth)
{
var properties = new List<object>();
var so = new SerializedObject(component);
var iterator = so.GetIterator();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
if (iterator.depth > depth)
{
enterChildren = false;
continue;
}
enterChildren = true;
var propInfo = new Dictionary<string, object>
{
["path"] = iterator.propertyPath,
["name"] = iterator.name,
["type"] = iterator.propertyType.ToString(),
["editable"] = iterator.editable
};
// Get value based on type
switch (iterator.propertyType)
{
case SerializedPropertyType.Integer:
propInfo["value"] = iterator.intValue;
break;
case SerializedPropertyType.Float:
propInfo["value"] = iterator.floatValue;
break;
case SerializedPropertyType.Boolean:
propInfo["value"] = iterator.boolValue;
break;
case SerializedPropertyType.String:
propInfo["value"] = iterator.stringValue;
break;
case SerializedPropertyType.Enum:
propInfo["value"] = iterator.enumDisplayNames?.Length > iterator.enumValueIndex && iterator.enumValueIndex >= 0
? iterator.enumDisplayNames[iterator.enumValueIndex]
: iterator.enumValueIndex.ToString();
propInfo["options"] = iterator.enumDisplayNames;
break;
case SerializedPropertyType.Vector2:
propInfo["value"] = new { x = iterator.vector2Value.x, y = iterator.vector2Value.y };
break;
case SerializedPropertyType.Vector3:
propInfo["value"] = new { x = iterator.vector3Value.x, y = iterator.vector3Value.y, z = iterator.vector3Value.z };
break;
case SerializedPropertyType.Vector4:
propInfo["value"] = new { x = iterator.vector4Value.x, y = iterator.vector4Value.y, z = iterator.vector4Value.z, w = iterator.vector4Value.w };
break;
case SerializedPropertyType.Color:
propInfo["value"] = new { r = iterator.colorValue.r, g = iterator.colorValue.g, b = iterator.colorValue.b, a = iterator.colorValue.a };
break;
case SerializedPropertyType.ObjectReference:
propInfo["value"] = iterator.objectReferenceValue?.name ?? "null";
propInfo["objectType"] = iterator.objectReferenceValue?.GetType().Name ?? "null";
break;
case SerializedPropertyType.LayerMask:
propInfo["value"] = iterator.intValue;
break;
case SerializedPropertyType.Rect:
var rect = iterator.rectValue;
propInfo["value"] = new { x = rect.x, y = rect.y, width = rect.width, height = rect.height };
break;
case SerializedPropertyType.ArraySize:
propInfo["value"] = iterator.intValue;
break;
case SerializedPropertyType.AnimationCurve:
propInfo["value"] = $"AnimationCurve with {iterator.animationCurveValue?.keys?.Length ?? 0} keys";
break;
default:
propInfo["value"] = "(complex type)";
break;
}
properties.Add(propInfo);
}
return properties;
}
private string InspectScene()
{
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var rootObjects = scene.GetRootGameObjects();
int totalObjects = 0;
int totalComponents = 0;
var componentCounts = new Dictionary<string, int>();
void CountRecursive(GameObject go)
{
totalObjects++;
foreach (var comp in go.GetComponents<Component>())
{
if (comp == null) continue;
totalComponents++;
var typeName = comp.GetType().Name;
componentCounts[typeName] = componentCounts.GetValueOrDefault(typeName, 0) + 1;
}
foreach (Transform child in go.transform)
{
CountRecursive(child.gameObject);
}
}
foreach (var root in rootObjects)
{
CountRecursive(root);
}
return JsonConvert.SerializeObject(new {
success = true,
scene = new {
name = scene.name,
path = scene.path,
isDirty = scene.isDirty,
rootCount = rootObjects.Length,
totalGameObjects = totalObjects,
totalComponents = totalComponents,
topComponents = componentCounts
.OrderByDescending(x => x.Value)
.Take(20)
.Select(x => new { type = x.Key, count = x.Value })
.ToList()
}
});
}
private string InspectHierarchy(int depth)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var rootObjects = scene.GetRootGameObjects();
object BuildHierarchy(GameObject go, int currentDepth)
{
var children = new List<object>();
if (currentDepth < depth)
{
foreach (Transform child in go.transform)
{
children.Add(BuildHierarchy(child.gameObject, currentDepth + 1));
}
}
return new {
name = go.name,
active = go.activeSelf,
components = go.GetComponents<Component>()
.Where(c => c != null)
.Select(c => c.GetType().Name)
.ToList(),
children = children.Count > 0 ? children : null
};
}
var hierarchy = rootObjects.Select(go => BuildHierarchy(go, 0)).ToList();
return JsonConvert.SerializeObject(new {
success = true,
scene = scene.name,
depth = depth,
hierarchy = hierarchy
});
}
private string InspectPrefabs(string pathFilter)
{
try
{
var searchPath = string.IsNullOrEmpty(pathFilter) ? "Assets" : pathFilter.Replace("*", "");
if (searchPath.EndsWith("/")) searchPath = searchPath.TrimEnd('/');
if (!searchPath.StartsWith("Assets")) searchPath = "Assets";
var guids = AssetDatabase.FindAssets("t:Prefab", new[] { searchPath });
var prefabs = guids
.Select(guid => AssetDatabase.GUIDToAssetPath(guid))
.Where(p => string.IsNullOrEmpty(pathFilter) || MatchesWildcardDynamic(p, pathFilter))
.Take(100)
.Select(p => {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(p);
return new {
path = p,
name = prefab?.name ?? Path.GetFileNameWithoutExtension(p),
components = prefab?.GetComponents<Component>()
.Where(c => c != null)
.Select(c => c.GetType().Name)
.ToList() ?? new List<string>()
};
})
.ToList();
return JsonConvert.SerializeObject(new {
success = true,
filter = pathFilter ?? "all",
count = prefabs.Count,
prefabs = prefabs
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { error = e.Message });
}
}
private string InspectProject(string pathFilter)
{
try
{
var searchPath = string.IsNullOrEmpty(pathFilter) ? "Assets" : pathFilter;
if (!searchPath.StartsWith("Assets")) searchPath = "Assets/" + searchPath;
searchPath = searchPath.TrimEnd('/');
// Get folder structure
string[] folders = new string[0];
try
{
folders = AssetDatabase.GetSubFolders(searchPath);
}
catch { }
// Get assets in current folder
var guids = AssetDatabase.FindAssets("", new[] { searchPath });
var assets = guids
.Select(guid => AssetDatabase.GUIDToAssetPath(guid))
.Where(p => Path.GetDirectoryName(p).Replace("\\", "/") == searchPath)
.Take(50)
.Select(p => new {
path = p,
name = Path.GetFileName(p),
type = AssetDatabase.GetMainAssetTypeAtPath(p)?.Name ?? "Unknown"
})
.ToList();
return JsonConvert.SerializeObject(new {
success = true,
currentPath = searchPath,
folders = folders,
assets = assets
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { error = e.Message });
}
}
private bool MatchesWildcardDynamic(string path, string pattern)
{
if (string.IsNullOrEmpty(pattern)) return true;
var regex = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
.Replace("\\*\\*", ".*")
.Replace("\\*", "[^/]*")
.Replace("\\?", ".") + "$";
return System.Text.RegularExpressions.Regex.IsMatch(path, regex, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
}
private GameObject FindGameObjectByPathDynamic(string path)
{
if (string.IsNullOrEmpty(path)) return null;
var parts = path.Split('/');
GameObject current = null;
foreach (var part in parts)
{
if (current == null)
{
current = GameObject.Find(part);
if (current == null)
{
var roots = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
current = roots.FirstOrDefault(r => r.name == part);
}
}
else
{
var child = current.transform.Find(part);
current = child?.gameObject;
}
if (current == null) return null;
}
return current;
}
/// <summary>
/// Dynamically modify any property of a Unity component
/// </summary>
private string DynamicModify(Dictionary<string, string> parameters)
{
try
{
var gameObjectName = parameters.GetValueOrDefault("gameObject", "");
var componentType = parameters.GetValueOrDefault("component", "");
var propertiesJson = parameters.GetValueOrDefault("properties", "{}");
bool.TryParse(parameters.GetValueOrDefault("createIfMissing", "false"), out bool createIfMissing);
if (string.IsNullOrEmpty(gameObjectName))
{
return JsonConvert.SerializeObject(new { error = "GameObject name required" });
}
var gameObject = GameObject.Find(gameObjectName) ?? FindGameObjectByPathDynamic(gameObjectName);
if (gameObject == null)
{
return JsonConvert.SerializeObject(new { error = $"GameObject '{gameObjectName}' not found" });
}
// Find or create component
Component component = null;
if (!string.IsNullOrEmpty(componentType))
{
component = gameObject.GetComponents<Component>()
.FirstOrDefault(c => c != null &&
(c.GetType().Name.Equals(componentType, StringComparison.OrdinalIgnoreCase) ||
c.GetType().Name.EndsWith(componentType, StringComparison.OrdinalIgnoreCase)));
if (component == null && createIfMissing)
{
var type = FindComponentTypeDynamic(componentType);
if (type != null)
{
component = gameObject.AddComponent(type);
}
}
if (component == null)
{
return JsonConvert.SerializeObject(new {
error = $"Component '{componentType}' not found on '{gameObjectName}'",
hint = createIfMissing ? "Could not create component - type not found" : "Use createIfMissing:true to add it"
});
}
}
else
{
return JsonConvert.SerializeObject(new { error = "Component type required" });
}
// Parse properties
var properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(propertiesJson);
if (properties == null || properties.Count == 0)
{
return JsonConvert.SerializeObject(new { error = "No properties specified" });
}
var so = new SerializedObject(component);
var modifiedProperties = new List<string>();
var failedProperties = new List<object>();
foreach (var kvp in properties)
{
var prop = so.FindProperty(kvp.Key);
if (prop == null)
{
failedProperties.Add(new { path = kvp.Key, error = "Property not found" });
continue;
}
if (!prop.editable)
{
failedProperties.Add(new { path = kvp.Key, error = "Property not editable" });
continue;
}
try
{
SetSerializedPropertyValueDynamic(prop, kvp.Value);
modifiedProperties.Add(kvp.Key);
}
catch (Exception e)
{
failedProperties.Add(new { path = kvp.Key, error = e.Message });
}
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(gameObject);
return JsonConvert.SerializeObject(new {
success = true,
gameObject = gameObjectName,
component = componentType,
modifiedProperties = modifiedProperties,
failedProperties = failedProperties.Count > 0 ? failedProperties : null
});
}
catch (Exception e)
{
return CreateErrorResponse("DynamicModify", e, parameters);
}
}
private void SetSerializedPropertyValueDynamic(SerializedProperty prop, object value)
{
switch (prop.propertyType)
{
case SerializedPropertyType.Integer:
prop.intValue = Convert.ToInt32(value);
break;
case SerializedPropertyType.Float:
prop.floatValue = Convert.ToSingle(value);
break;
case SerializedPropertyType.Boolean:
prop.boolValue = Convert.ToBoolean(value);
break;
case SerializedPropertyType.String:
prop.stringValue = value?.ToString() ?? "";
break;
case SerializedPropertyType.Enum:
if (value is int intVal)
prop.enumValueIndex = intVal;
else if (value is long longVal)
prop.enumValueIndex = (int)longVal;
else if (value is string strVal)
{
var idx = Array.IndexOf(prop.enumDisplayNames, strVal);
if (idx >= 0) prop.enumValueIndex = idx;
else if (int.TryParse(strVal, out int parsed)) prop.enumValueIndex = parsed;
}
break;
case SerializedPropertyType.Vector2:
if (value is Newtonsoft.Json.Linq.JObject v2)
prop.vector2Value = new Vector2(v2["x"]?.ToObject<float>() ?? 0, v2["y"]?.ToObject<float>() ?? 0);
break;
case SerializedPropertyType.Vector3:
if (value is Newtonsoft.Json.Linq.JObject v3)
prop.vector3Value = new Vector3(v3["x"]?.ToObject<float>() ?? 0, v3["y"]?.ToObject<float>() ?? 0, v3["z"]?.ToObject<float>() ?? 0);
break;
case SerializedPropertyType.Vector4:
if (value is Newtonsoft.Json.Linq.JObject v4)
prop.vector4Value = new Vector4(v4["x"]?.ToObject<float>() ?? 0, v4["y"]?.ToObject<float>() ?? 0, v4["z"]?.ToObject<float>() ?? 0, v4["w"]?.ToObject<float>() ?? 0);
break;
case SerializedPropertyType.Color:
if (value is Newtonsoft.Json.Linq.JObject c)
prop.colorValue = new Color(c["r"]?.ToObject<float>() ?? 1, c["g"]?.ToObject<float>() ?? 1, c["b"]?.ToObject<float>() ?? 1, c["a"]?.ToObject<float>() ?? 1);
else if (value is string hex)
prop.colorValue = ParseHexColorDynamic(hex);
break;
case SerializedPropertyType.LayerMask:
prop.intValue = Convert.ToInt32(value);
break;
default:
throw new NotSupportedException($"Property type {prop.propertyType} not supported for direct modification");
}
}
private Color ParseHexColorDynamic(string hex)
{
if (string.IsNullOrEmpty(hex)) return Color.white;
hex = hex.TrimStart('#');
if (hex.Length == 6)
{
byte r = Convert.ToByte(hex.Substring(0, 2), 16);
byte g = Convert.ToByte(hex.Substring(2, 2), 16);
byte b = Convert.ToByte(hex.Substring(4, 2), 16);
return new Color32(r, g, b, 255);
}
return Color.white;
}
private Type FindComponentTypeDynamic(string typeName)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var type = assembly.GetTypes()
.FirstOrDefault(t => typeof(Component).IsAssignableFrom(t) &&
(t.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase) ||
t.Name.EndsWith(typeName, StringComparison.OrdinalIgnoreCase)));
if (type != null) return type;
}
catch { }
}
return null;
}
/// <summary>
/// Universal creation tool for GameObjects, prefabs, scenes, and components
/// </summary>
private string DynamicCreate(Dictionary<string, string> parameters)
{
try
{
var createType = parameters.GetValueOrDefault("type", "gameobject").ToLower();
switch (createType)
{
case "gameobject":
return CreateDynamicGameObject(parameters);
case "prefab":
return CreateDynamicPrefabInstance(parameters);
case "scene":
return LoadDynamicScene(parameters);
case "component":
return AddDynamicComponent(parameters);
default:
return JsonConvert.SerializeObject(new { error = $"Unknown create type: {createType}" });
}
}
catch (Exception e)
{
return CreateErrorResponse("DynamicCreate", e, parameters);
}
}
private string CreateDynamicGameObject(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "New GameObject");
var primitive = parameters.GetValueOrDefault("primitive", "empty").ToLower();
var parentName = parameters.GetValueOrDefault("parent", "");
GameObject go;
switch (primitive)
{
case "cube": go = GameObject.CreatePrimitive(PrimitiveType.Cube); break;
case "sphere": go = GameObject.CreatePrimitive(PrimitiveType.Sphere); break;
case "cylinder": go = GameObject.CreatePrimitive(PrimitiveType.Cylinder); break;
case "plane": go = GameObject.CreatePrimitive(PrimitiveType.Plane); break;
case "capsule": go = GameObject.CreatePrimitive(PrimitiveType.Capsule); break;
case "quad": go = GameObject.CreatePrimitive(PrimitiveType.Quad); break;
default: go = new GameObject(); break;
}
go.name = name;
if (!string.IsNullOrEmpty(parentName))
{
var parent = GameObject.Find(parentName) ?? FindGameObjectByPathDynamic(parentName);
if (parent != null)
{
go.transform.SetParent(parent.transform, false);
}
}
ApplyTransformFromParametersDynamic(go, parameters);
Undo.RegisterCreatedObjectUndo(go, $"Create {name}");
Selection.activeGameObject = go;
return JsonConvert.SerializeObject(new {
success = true,
message = $"Created GameObject '{name}'",
gameObject = new {
name = go.name,
primitive = primitive,
path = GetGameObjectPathDynamic(go)
}
});
}
private string CreateDynamicPrefabInstance(Dictionary<string, string> parameters)
{
var assetPath = parameters.GetValueOrDefault("asset", "");
var instanceName = parameters.GetValueOrDefault("name", "");
var parentName = parameters.GetValueOrDefault("parent", "");
if (string.IsNullOrEmpty(assetPath))
{
return JsonConvert.SerializeObject(new { error = "Asset path required" });
}
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (prefab == null)
{
var guids = AssetDatabase.FindAssets($"{Path.GetFileNameWithoutExtension(assetPath)} t:Prefab");
if (guids.Length > 0)
{
var foundPath = AssetDatabase.GUIDToAssetPath(guids[0]);
prefab = AssetDatabase.LoadAssetAtPath<GameObject>(foundPath);
}
}
if (prefab == null)
{
return JsonConvert.SerializeObject(new { error = $"Prefab not found at '{assetPath}'" });
}
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
if (!string.IsNullOrEmpty(instanceName))
{
instance.name = instanceName;
}
if (!string.IsNullOrEmpty(parentName))
{
var parent = GameObject.Find(parentName) ?? FindGameObjectByPathDynamic(parentName);
if (parent != null)
{
instance.transform.SetParent(parent.transform, false);
}
}
ApplyTransformFromParametersDynamic(instance, parameters);
Undo.RegisterCreatedObjectUndo(instance, $"Instantiate {prefab.name}");
Selection.activeGameObject = instance;
return JsonConvert.SerializeObject(new {
success = true,
message = $"Instantiated prefab '{prefab.name}'",
instance = new {
name = instance.name,
prefabPath = assetPath,
path = GetGameObjectPathDynamic(instance)
}
});
}
private string LoadDynamicScene(Dictionary<string, string> parameters)
{
var sceneName = parameters.GetValueOrDefault("scene", "");
bool.TryParse(parameters.GetValueOrDefault("additive", "false"), out bool additive);
if (string.IsNullOrEmpty(sceneName))
{
return JsonConvert.SerializeObject(new { error = "Scene name required" });
}
string scenePath = sceneName;
if (!sceneName.EndsWith(".unity"))
{
var guids = AssetDatabase.FindAssets($"{sceneName} t:Scene");
if (guids.Length > 0)
{
scenePath = AssetDatabase.GUIDToAssetPath(guids[0]);
}
else
{
foreach (var buildScene in EditorBuildSettings.scenes)
{
if (Path.GetFileNameWithoutExtension(buildScene.path).Equals(sceneName, StringComparison.OrdinalIgnoreCase))
{
scenePath = buildScene.path;
break;
}
}
}
}
if (!File.Exists(scenePath))
{
return JsonConvert.SerializeObject(new { error = $"Scene '{sceneName}' not found" });
}
var mode = additive ? UnityEditor.SceneManagement.OpenSceneMode.Additive : UnityEditor.SceneManagement.OpenSceneMode.Single;
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, mode);
return JsonConvert.SerializeObject(new {
success = true,
message = $"Loaded scene '{scene.name}' {(additive ? "additively" : "")}",
scene = new {
name = scene.name,
path = scene.path,
rootCount = scene.rootCount
}
});
}
private string AddDynamicComponent(Dictionary<string, string> parameters)
{
var gameObjectName = parameters.GetValueOrDefault("gameObject", "");
var componentType = parameters.GetValueOrDefault("component", "");
if (string.IsNullOrEmpty(gameObjectName))
{
return JsonConvert.SerializeObject(new { error = "GameObject name required" });
}
if (string.IsNullOrEmpty(componentType))
{
return JsonConvert.SerializeObject(new { error = "Component type required" });
}
var gameObject = GameObject.Find(gameObjectName) ?? FindGameObjectByPathDynamic(gameObjectName);
if (gameObject == null)
{
return JsonConvert.SerializeObject(new { error = $"GameObject '{gameObjectName}' not found" });
}
var type = FindComponentTypeDynamic(componentType);
if (type == null)
{
return JsonConvert.SerializeObject(new { error = $"Component type '{componentType}' not found" });
}
if (gameObject.GetComponent(type) != null)
{
return JsonConvert.SerializeObject(new {
success = true,
message = $"Component '{componentType}' already exists on '{gameObjectName}'",
alreadyExists = true
});
}
var component = Undo.AddComponent(gameObject, type);
return JsonConvert.SerializeObject(new {
success = true,
message = $"Added component '{type.Name}' to '{gameObjectName}'",
component = new {
type = type.Name,
fullType = type.FullName
}
});
}
private void ApplyTransformFromParametersDynamic(GameObject go, Dictionary<string, string> parameters)
{
if (parameters.TryGetValue("position", out var posJson))
{
try
{
var pos = JsonConvert.DeserializeObject<Dictionary<string, float>>(posJson);
if (pos != null)
{
go.transform.position = new Vector3(
pos.GetValueOrDefault("x", 0),
pos.GetValueOrDefault("y", 0),
pos.GetValueOrDefault("z", 0)
);
}
}
catch { }
}
if (parameters.TryGetValue("rotation", out var rotJson))
{
try
{
var rot = JsonConvert.DeserializeObject<Dictionary<string, float>>(rotJson);
if (rot != null)
{
go.transform.eulerAngles = new Vector3(
rot.GetValueOrDefault("x", 0),
rot.GetValueOrDefault("y", 0),
rot.GetValueOrDefault("z", 0)
);
}
}
catch { }
}
if (parameters.TryGetValue("scale", out var scaleJson))
{
try
{
var scale = JsonConvert.DeserializeObject<Dictionary<string, float>>(scaleJson);
if (scale != null)
{
go.transform.localScale = new Vector3(
scale.GetValueOrDefault("x", 1),
scale.GetValueOrDefault("y", 1),
scale.GetValueOrDefault("z", 1)
);
}
}
catch { }
}
}
private string GetGameObjectPathDynamic(GameObject go)
{
string path = go.name;
Transform current = go.transform.parent;
while (current != null)
{
path = current.name + "/" + path;
current = current.parent;
}
return path;
}
#endregion
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: ed1edec647066482b8f641d3e27f7721
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusExecutorDynamicTools.cs
uploadId: 920982
@@ -1,139 +0,0 @@
using System;
using UnityEditor;
using UnityEngine;
namespace SynapticPro
{
/// <summary>
/// Window to manage operation history and Undo/Redo
/// </summary>
public class NexusHistoryWindow : EditorWindow
{
// [MenuItem("Window/Synaptic Pro/📜 Operation History")]
public static void ShowWindow()
{
var window = GetWindow<NexusHistoryWindow>("📜 Operation History");
window.minSize = new Vector2(300, 400);
window.Show();
}
private Vector2 scrollPosition;
private bool autoRefresh = true;
private void OnEnable()
{
NexusOperationHistory.Instance.OnHistoryChanged += Repaint;
}
private void OnDisable()
{
NexusOperationHistory.Instance.OnHistoryChanged -= Repaint;
}
private void OnGUI()
{
DrawHeader();
DrawControls();
DrawHistoryInfo();
}
private void DrawHeader()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.Label("Operation History", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
autoRefresh = GUILayout.Toggle(autoRefresh, "Auto Refresh", EditorStyles.toolbarButton, GUILayout.Width(90));
if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
{
Repaint();
}
EditorGUILayout.EndHorizontal();
}
private void DrawControls()
{
EditorGUILayout.BeginHorizontal();
// Undo button
GUI.enabled = NexusOperationHistory.Instance.CanUndo;
if (GUILayout.Button("↶ Undo", GUILayout.Height(30)))
{
if (NexusOperationHistory.Instance.Undo())
{
EditorUtility.DisplayDialog("Undo", "Operation undone", "OK");
}
}
// Redo button
GUI.enabled = NexusOperationHistory.Instance.CanRedo;
if (GUILayout.Button("↷ Redo", GUILayout.Height(30)))
{
if (NexusOperationHistory.Instance.Redo())
{
EditorUtility.DisplayDialog("Redo", "Operation redone", "OK");
}
}
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
// Clear history
if (GUILayout.Button("Clear History", GUILayout.Height(25)))
{
if (EditorUtility.DisplayDialog("Confirm", "Delete all history?", "Delete", "Cancel"))
{
NexusOperationHistory.Instance.ClearHistory();
}
}
// Export
if (GUILayout.Button("Export", GUILayout.Height(25)))
{
ExportHistory();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
}
private void DrawHistoryInfo()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
var info = NexusOperationHistory.Instance.GetHistoryInfo();
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.TextArea(info, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void ExportHistory()
{
var path = EditorUtility.SaveFilePanel(
"Export History",
Application.dataPath,
$"NexusHistory_{DateTime.Now:yyyyMMdd_HHmmss}.json",
"json"
);
if (!string.IsNullOrEmpty(path))
{
var json = NexusOperationHistory.Instance.ExportHistory();
System.IO.File.WriteAllText(path, json);
EditorUtility.DisplayDialog("Export Complete", "History has been exported", "OK");
}
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 938cf1b3a071149eba7cc38716a66904
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusHistoryWindow.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 004694d47152b4d8bbc100abd35eaf4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusMainWindow.cs
uploadId: 920982
@@ -1,479 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using SynapticAIPro;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Project planning and Todo management window
/// Displays and manages AI-generated plans
/// </summary>
public class NexusProjectPlannerWindow : EditorWindow
{
// [MenuItem("Window/Synaptic Pro/📋 Project Planner")]
public static void ShowWindow()
{
var window = GetWindow<NexusProjectPlannerWindow>("📋 Project Planner");
window.minSize = new Vector2(600, 400);
window.Show();
}
private Vector2 scrollPosition;
private ProjectPlan currentPlan;
private List<ProjectTask> tasks = new List<ProjectTask>();
private string newTaskInput = "";
private int selectedTaskIndex = -1;
// Styles
private GUIStyle headerStyle;
private GUIStyle taskStyle;
private GUIStyle completedTaskStyle;
private GUIStyle phaseStyle;
[Serializable]
public class ProjectPlan
{
public string title = "New Project";
public string overview = "";
public List<ProjectPhase> phases = new List<ProjectPhase>();
public string currentPhase = "planning";
public float progress = 0f;
}
[Serializable]
public class ProjectPhase
{
public string name;
public List<string> tasks;
public bool isCompleted;
}
[Serializable]
public class ProjectTask
{
public int id;
public string name;
public string description;
public string status = "pending"; // pending, in_progress, completed
public string priority = "medium"; // low, medium, high
public List<ProjectTask> subtasks;
public DateTime createdAt;
public DateTime? completedAt;
}
private void OnEnable()
{
// Setup WebSocket message reception
NexusWebSocketClient.Instance.OnMessageReceived += OnWebSocketMessage;
// Load saved data
LoadProjectData();
}
private void OnDisable()
{
NexusWebSocketClient.Instance.OnMessageReceived -= OnWebSocketMessage;
// Save data
SaveProjectData();
}
private void InitializeStyles()
{
headerStyle = new GUIStyle(EditorStyles.largeLabel)
{
fontSize = 20,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter
};
taskStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 14,
padding = new RectOffset(20, 10, 5, 5),
wordWrap = true
};
completedTaskStyle = new GUIStyle(taskStyle)
{
normal = { textColor = new Color(0.5f, 0.5f, 0.5f) },
fontStyle = FontStyle.Italic
};
phaseStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 16,
padding = new RectOffset(10, 10, 10, 5)
};
}
private void OnGUI()
{
if (headerStyle == null)
InitializeStyles();
DrawHeader();
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
if (currentPlan != null)
{
DrawProjectOverview();
DrawPhases();
}
DrawTaskList();
DrawTaskInput();
EditorGUILayout.EndScrollView();
}
private void DrawHeader()
{
EditorGUILayout.Space(10);
GUILayout.Label("📋 Project Planner", headerStyle, GUILayout.Height(30));
if (currentPlan != null)
{
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
// Progress bar
var rect = GUILayoutUtility.GetRect(300, 20);
EditorGUI.ProgressBar(rect, currentPlan.progress, $"Progress: {currentPlan.progress * 100:F0}%");
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space(10);
}
private void DrawProjectOverview()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
// Project title
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Project:", GUILayout.Width(80));
currentPlan.title = EditorGUILayout.TextField(currentPlan.title);
EditorGUILayout.EndHorizontal();
// Overview
GUILayout.Label("Overview:", EditorStyles.boldLabel);
currentPlan.overview = EditorGUILayout.TextArea(currentPlan.overview, GUILayout.Height(60));
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
}
private void DrawPhases()
{
if (currentPlan.phases.Count == 0) return;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label("📅 Development Phases", EditorStyles.boldLabel);
foreach (var phase in currentPlan.phases)
{
EditorGUILayout.BeginHorizontal();
// Checkbox
bool wasCompleted = phase.isCompleted;
phase.isCompleted = EditorGUILayout.Toggle(phase.isCompleted, GUILayout.Width(20));
if (wasCompleted != phase.isCompleted)
{
UpdateProgress();
}
// Phase name
var style = phase.isCompleted ? completedTaskStyle : phaseStyle;
GUILayout.Label(phase.name, style);
EditorGUILayout.EndHorizontal();
// Phase tasks
if (phase.tasks != null && phase.tasks.Count > 0)
{
EditorGUI.indentLevel++;
foreach (var task in phase.tasks)
{
EditorGUILayout.LabelField($"• {task}", EditorStyles.miniLabel);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.Space(5);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
}
private void DrawTaskList()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label("✅ Task List", EditorStyles.boldLabel);
if (tasks.Count == 0)
{
EditorGUILayout.HelpBox("No tasks available. Try telling AI \"I want to create something like...\"", MessageType.Info);
}
else
{
for (int i = 0; i < tasks.Count; i++)
{
DrawTask(tasks[i], i, 0);
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
}
private void DrawTask(ProjectTask task, int index, int indent)
{
EditorGUILayout.BeginHorizontal();
// Indent
GUILayout.Space(indent * 20);
// Checkbox
bool wasCompleted = task.status == "completed";
bool isCompleted = EditorGUILayout.Toggle(wasCompleted, GUILayout.Width(20));
if (wasCompleted != isCompleted)
{
task.status = isCompleted ? "completed" : "pending";
task.completedAt = isCompleted ? DateTime.Now : (DateTime?)null;
UpdateProgress();
}
// Priority indicator
var priorityColor = task.priority == "high" ? Color.red :
task.priority == "medium" ? Color.yellow :
Color.green;
var oldColor = GUI.color;
GUI.color = priorityColor;
GUILayout.Label("●", GUILayout.Width(15));
GUI.color = oldColor;
// Task name
var style = task.status == "completed" ? completedTaskStyle : taskStyle;
if (GUILayout.Button(task.name, style))
{
selectedTaskIndex = index;
}
// Status
var statusIcon = task.status == "completed" ? "✅" :
task.status == "in_progress" ? "🔄" : "⏳";
GUILayout.Label(statusIcon, GUILayout.Width(25));
// Delete button
if (GUILayout.Button("×", GUILayout.Width(20)))
{
tasks.Remove(task);
}
EditorGUILayout.EndHorizontal();
// Selected task details
if (selectedTaskIndex == index && !string.IsNullOrEmpty(task.description))
{
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox(task.description, MessageType.None);
EditorGUI.indentLevel--;
}
// Subtasks
if (task.subtasks != null && task.subtasks.Count > 0)
{
foreach (var subtask in task.subtasks)
{
DrawTask(subtask, -1, indent + 1);
}
}
}
private void DrawTaskInput()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("New Task:", GUILayout.Width(80));
newTaskInput = EditorGUILayout.TextField(newTaskInput);
if (GUILayout.Button("Add", GUILayout.Width(60)))
{
if (!string.IsNullOrEmpty(newTaskInput))
{
AddTask(newTaskInput);
newTaskInput = "";
GUI.FocusControl(null);
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void AddTask(string taskName, string priority = "medium")
{
var task = new ProjectTask
{
id = tasks.Count + 1,
name = taskName,
status = "pending",
priority = priority,
createdAt = DateTime.Now,
subtasks = new List<ProjectTask>()
};
tasks.Add(task);
SaveProjectData();
}
private void OnWebSocketMessage(string message)
{
try
{
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(message);
if (data != null && data.ContainsKey("type"))
{
var type = data["type"].ToString();
switch (type)
{
case "project_plan":
UpdateProjectPlan(data);
break;
case "task_list":
UpdateTaskList(data);
break;
case "task_update":
UpdateTask(data);
break;
}
}
}
catch (Exception e)
{
Debug.LogError($"[Project Planner] Error processing message: {e.Message}");
}
}
private void UpdateProjectPlan(Dictionary<string, object> data)
{
if (data.ContainsKey("plan"))
{
var planData = data["plan"] as Newtonsoft.Json.Linq.JObject;
if (planData != null)
{
currentPlan = planData.ToObject<ProjectPlan>();
Repaint();
}
}
}
private void UpdateTaskList(Dictionary<string, object> data)
{
if (data.ContainsKey("tasks"))
{
var tasksData = data["tasks"] as Newtonsoft.Json.Linq.JArray;
if (tasksData != null)
{
tasks = tasksData.ToObject<List<ProjectTask>>();
Repaint();
}
}
}
private void UpdateTask(Dictionary<string, object> data)
{
if (data.ContainsKey("taskId") && data.ContainsKey("status"))
{
int taskId = Convert.ToInt32(data["taskId"]);
string status = data["status"].ToString();
var task = tasks.Find(t => t.id == taskId);
if (task != null)
{
task.status = status;
if (status == "completed")
{
task.completedAt = DateTime.Now;
}
UpdateProgress();
Repaint();
}
}
}
private void UpdateProgress()
{
if (currentPlan != null)
{
int totalTasks = tasks.Count;
int completedTasks = tasks.FindAll(t => t.status == "completed").Count;
if (totalTasks > 0)
{
currentPlan.progress = (float)completedTasks / totalTasks;
}
// Also consider phase progress
if (currentPlan.phases.Count > 0)
{
int completedPhases = currentPlan.phases.FindAll(p => p.isCompleted).Count;
float phaseProgress = (float)completedPhases / currentPlan.phases.Count;
// Overall progress is average of task progress and phase progress
currentPlan.progress = (currentPlan.progress + phaseProgress) / 2f;
}
}
}
private void LoadProjectData()
{
// Load saved data from EditorPrefs
string planJson = EditorPrefs.GetString("NexusProjectPlan", "");
if (!string.IsNullOrEmpty(planJson))
{
currentPlan = JsonConvert.DeserializeObject<ProjectPlan>(planJson);
}
else
{
currentPlan = new ProjectPlan();
}
string tasksJson = EditorPrefs.GetString("NexusProjectTasks", "");
if (!string.IsNullOrEmpty(tasksJson))
{
tasks = JsonConvert.DeserializeObject<List<ProjectTask>>(tasksJson);
}
}
private void SaveProjectData()
{
// Save to EditorPrefs
if (currentPlan != null)
{
EditorPrefs.SetString("NexusProjectPlan", JsonConvert.SerializeObject(currentPlan));
}
if (tasks != null && tasks.Count > 0)
{
EditorPrefs.SetString("NexusProjectTasks", JsonConvert.SerializeObject(tasks));
}
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 8b169d81e845c4bd8b84f19675d03062
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusProjectPlannerWindow.cs
uploadId: 920982
@@ -1,420 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using SynapticAIPro;
using UnityEditor;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Port management system per project
/// Assigns and manages unique ports for each Unity project
/// </summary>
[InitializeOnLoad]
public static class NexusProjectPortManager
{
private static string projectId;
private static int assignedPort = -1;
private static readonly string MAPPING_FILE_PATH;
// Project-port mapping information
[Serializable]
public class ProjectPortMapping
{
public Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>();
}
[Serializable]
public class ProjectInfo
{
public string projectName;
public string projectPath;
public int port;
public DateTime lastUpdated;
public bool isActive;
}
static NexusProjectPortManager()
{
// Mapping file path (saved in user home directory)
string homeDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
MAPPING_FILE_PATH = Path.Combine(homeDir, ".config", "nexus", "project_port_mapping.json");
// Initialize
Initialize();
}
private static void Initialize()
{
// Generate project ID (hash value of project path)
string projectPath = Application.dataPath;
projectId = GetProjectId(projectPath);
SynLog.Info($"[Nexus Port Manager] Project ID: {projectId}");
SynLog.Info($"[Nexus Port Manager] Project Path: {projectPath}");
// Assign port
AssignPort();
// Update status periodically
EditorApplication.update += UpdateProjectStatus;
EditorApplication.quitting += OnEditorQuitting;
}
/// <summary>
/// Generate project ID
/// </summary>
private static string GetProjectId(string projectPath)
{
// Generate unique ID from project path
using (var md5 = System.Security.Cryptography.MD5.Create())
{
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(projectPath);
byte[] hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant().Substring(0, 8);
}
}
/// <summary>
/// Assign port
/// </summary>
private static void AssignPort()
{
var mapping = LoadMapping();
// Check existing port assignment
if (mapping.projects.ContainsKey(projectId))
{
var info = mapping.projects[projectId];
assignedPort = info.port;
info.lastUpdated = DateTime.Now;
info.isActive = true;
info.projectName = PlayerSettings.productName;
SynLog.Info($"[Nexus Port Manager] Using existing port: {assignedPort}");
}
else
{
// Assign new port
assignedPort = FindAvailablePort(mapping);
mapping.projects[projectId] = new ProjectInfo
{
projectName = PlayerSettings.productName,
projectPath = Application.dataPath,
port = assignedPort,
lastUpdated = DateTime.Now,
isActive = true
};
SynLog.Info($"[Nexus Port Manager] Assigned new port: {assignedPort}");
}
SaveMapping(mapping);
// Notify port to NexusEditorMCPService
UpdateMCPServicePort();
}
/// <summary>
/// Find available port
/// </summary>
private static int FindAvailablePort(ProjectPortMapping mapping)
{
int[] candidatePorts = { 8090, 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099 };
foreach (int port in candidatePorts)
{
bool isUsed = false;
foreach (var project in mapping.projects.Values)
{
if (project.port == port && project.isActive)
{
isUsed = true;
break;
}
}
if (!isUsed)
{
return port;
}
}
// If all in use, search sequentially from 8090
return 8090 + mapping.projects.Count;
}
/// <summary>
/// Set port for MCP service
/// </summary>
private static void UpdateMCPServicePort()
{
if (assignedPort > 0)
{
// Check actual port of current MCP server
string currentUrl = NexusEditorMCPService.GetServerUrl();
if (!string.IsNullOrEmpty(currentUrl) && currentUrl.Contains("localhost:"))
{
// Get actually running port
int startIndex = currentUrl.IndexOf("localhost:") + "localhost:".Length;
int endIndex = currentUrl.IndexOf("/", startIndex);
if (endIndex == -1) endIndex = currentUrl.Length;
if (int.TryParse(currentUrl.Substring(startIndex, endIndex - startIndex), out int actualPort))
{
// If actual port differs from assigned port, prioritize actual port
if (actualPort != assignedPort && IsPortInUse(actualPort))
{
SynLog.Info($"[Nexus Port Manager] MCP Server is actually running on port {actualPort}, not {assignedPort}. Keeping actual port.");
assignedPort = actualPort;
// Update mapping
var mapping = LoadMapping();
if (mapping.projects.ContainsKey(projectId))
{
mapping.projects[projectId].port = actualPort;
SaveMapping(mapping);
}
return;
}
}
}
// Only use saved port if actual server not found
string serverUrl = $"ws://localhost:{assignedPort}";
NexusEditorMCPService.SetServerUrl(serverUrl);
SynLog.Info($"[Nexus Port Manager] Updated MCP Service URL: {serverUrl}");
}
}
/// <summary>
/// Check if specified port is in use
/// </summary>
private static bool IsPortInUse(int port)
{
try
{
using (var tcpClient = new System.Net.Sockets.TcpClient())
{
var result = tcpClient.BeginConnect("localhost", port, null, null);
bool success = result.AsyncWaitHandle.WaitOne(100);
if (success)
{
tcpClient.EndConnect(result);
tcpClient.Close();
return true;
}
return false;
}
}
catch
{
return false;
}
}
/// <summary>
/// Update project status
/// </summary>
private static void UpdateProjectStatus()
{
// Update status every 5 minutes
if (EditorApplication.timeSinceStartup % 300 < 1)
{
var mapping = LoadMapping();
if (mapping.projects.ContainsKey(projectId))
{
mapping.projects[projectId].lastUpdated = DateTime.Now;
mapping.projects[projectId].isActive = true;
SaveMapping(mapping);
}
}
}
/// <summary>
/// Processing on editor exit
/// </summary>
private static void OnEditorQuitting()
{
var mapping = LoadMapping();
if (mapping.projects.ContainsKey(projectId))
{
mapping.projects[projectId].isActive = false;
SaveMapping(mapping);
}
EditorApplication.update -= UpdateProjectStatus;
}
/// <summary>
/// Load mapping information
/// </summary>
private static ProjectPortMapping LoadMapping()
{
try
{
if (File.Exists(MAPPING_FILE_PATH))
{
string json = File.ReadAllText(MAPPING_FILE_PATH);
// Validate and clean JSON string
json = json.Trim();
if (string.IsNullOrEmpty(json))
{
SynLog.Warn("[Nexus Port Manager] Mapping file is empty, creating new mapping.");
return new ProjectPortMapping();
}
// Check JSON syntax
if (!json.StartsWith("{") || !json.EndsWith("}"))
{
Debug.LogError($"[Nexus Port Manager] Invalid JSON format in mapping file. Content: {json.Substring(0, Math.Min(100, json.Length))}...");
return new ProjectPortMapping();
}
var result = JsonConvert.DeserializeObject<ProjectPortMapping>(json);
return result ?? new ProjectPortMapping();
}
}
catch (JsonException jsonEx)
{
Debug.LogError($"[Nexus Port Manager] JSON parsing error: {jsonEx.Message}");
Debug.LogError($"[Nexus Port Manager] Recreating mapping file due to corruption.");
// Move-aside corrupted file, then write a fresh empty mapping so
// the next LoadMapping doesn't hit the same corrupt content and
// spam the console at frame rate.
// Bugs the previous implementation had:
// 1. `File.Move` to an existing `.backup` throws on Windows,
// the silent catch left the corrupt file in place → loop.
// 2. Even on success, no fresh file was written, so any later
// reader before a SaveMapping run kept hitting the error
// (relevant when MAPPING_FILE_PATH still existed under
// filesystem latency on slow disks).
try
{
string backupPath = MAPPING_FILE_PATH + ".backup";
if (File.Exists(backupPath))
{
try { File.Delete(backupPath); } catch { }
}
if (File.Exists(MAPPING_FILE_PATH))
{
try { File.Move(MAPPING_FILE_PATH, backupPath); }
catch
{
// Move failed (permissions, fs lock) — delete the
// corrupt source outright so we don't loop.
try { File.Delete(MAPPING_FILE_PATH); } catch { }
}
}
// Immediately materialize a clean mapping file so concurrent
// readers (and the next Update tick) see valid JSON.
var fresh = new ProjectPortMapping();
var freshJson = JsonConvert.SerializeObject(fresh, Formatting.Indented);
File.WriteAllText(MAPPING_FILE_PATH, freshJson);
SynLog.Info($"[Nexus Port Manager] Corrupted file backed up to: {backupPath}");
return fresh;
}
catch (Exception writeErr)
{
Debug.LogError($"[Nexus Port Manager] Failed to reset mapping file: {writeErr.Message}");
return new ProjectPortMapping();
}
}
catch (Exception e)
{
Debug.LogError($"[Nexus Port Manager] Failed to load mapping: {e.Message}");
}
return new ProjectPortMapping();
}
/// <summary>
/// Save mapping information
/// </summary>
private static void SaveMapping(ProjectPortMapping mapping)
{
try
{
// Clean up old entries (not updated for more than 30 days)
var keysToRemove = new List<string>();
foreach (var kvp in mapping.projects)
{
if ((DateTime.Now - kvp.Value.lastUpdated).TotalDays > 30)
{
keysToRemove.Add(kvp.Key);
}
}
foreach (var key in keysToRemove)
{
mapping.projects.Remove(key);
}
// Create directory
string directory = Path.GetDirectoryName(MAPPING_FILE_PATH);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// Save
string json = JsonConvert.SerializeObject(mapping, Formatting.Indented);
File.WriteAllText(MAPPING_FILE_PATH, json);
}
catch (Exception e)
{
Debug.LogError($"[Nexus Port Manager] Failed to save mapping: {e.Message}");
}
}
/// <summary>
/// Get port for current project
/// </summary>
public static int GetAssignedPort()
{
return assignedPort;
}
/// <summary>
/// Get current project ID
/// </summary>
public static string GetProjectId()
{
return projectId;
}
/// <summary>
/// Display mapping information (for debugging)
/// </summary>
[MenuItem("Tools/Synaptic Pro/Show Port Mapping")]
public static void ShowPortMapping()
{
var mapping = LoadMapping();
System.Text.StringBuilder info = new System.Text.StringBuilder();
info.AppendLine("🔌 Nexus Project Port Mapping");
info.AppendLine("================================");
info.AppendLine($"Current Project ID: {projectId}");
info.AppendLine($"Assigned Port: {assignedPort}");
info.AppendLine("\nAll Projects:");
foreach (var kvp in mapping.projects)
{
var project = kvp.Value;
info.AppendLine($"\n📁 {project.projectName}");
info.AppendLine($" ID: {kvp.Key}");
info.AppendLine($" Port: {project.port}");
info.AppendLine($" Path: {project.projectPath}");
info.AppendLine($" Active: {project.isActive}");
info.AppendLine($" Last Updated: {project.lastUpdated}");
}
SynLog.Info(info.ToString());
EditorUtility.DisplayDialog("Port Mapping", info.ToString(), "OK");
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: c59fc0a94d3554965805860d693abfee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusProjectPortManager.cs
uploadId: 920982
@@ -1,655 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using SynapticAIPro;
using UnityEditor;
using System.Text;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Unity project settings detailed retrieval and manipulation system
/// Provides complete information for Build Settings, Player Settings, Quality Settings, etc.
/// </summary>
public static class NexusProjectSettings
{
#region Build Settings
/// <summary>
/// Get detailed information for Build Settings
/// </summary>
public static string GetBuildSettings()
{
try
{
var buildSettings = new Dictionary<string, object>();
// Basic settings
buildSettings["target_group"] = EditorUserBuildSettings.selectedBuildTargetGroup.ToString();
buildSettings["build_target"] = EditorUserBuildSettings.activeBuildTarget.ToString();
buildSettings["development_build"] = EditorUserBuildSettings.development;
buildSettings["auto_connect_profiler"] = EditorUserBuildSettings.connectProfiler;
buildSettings["deep_profiling"] = EditorUserBuildSettings.buildWithDeepProfilingSupport;
buildSettings["script_debugging"] = EditorUserBuildSettings.allowDebugging;
// Scene settings
var scenes = EditorBuildSettings.scenes;
var sceneList = new List<Dictionary<string, object>>();
for (int i = 0; i < scenes.Length; i++)
{
var scene = scenes[i];
sceneList.Add(new Dictionary<string, object>
{
["index"] = i,
["path"] = scene.path,
["enabled"] = scene.enabled,
["guid"] = scene.guid.ToString()
});
}
buildSettings["scenes"] = sceneList;
// Platform-specific settings
var platformSettings = new Dictionary<string, object>();
// Android settings
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android)
{
platformSettings["android"] = new Dictionary<string, object>
{
["build_system"] = EditorUserBuildSettings.androidBuildSystem.ToString(),
["export_project"] = EditorUserBuildSettings.exportAsGoogleAndroidProject,
["build_app_bundle"] = EditorUserBuildSettings.buildAppBundle
};
}
// iOS settings
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS)
{
platformSettings["ios"] = new Dictionary<string, object>
{
["build_number"] = PlayerSettings.iOS.buildNumber
};
}
// WebGL settings
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.WebGL)
{
platformSettings["webgl"] = new Dictionary<string, object>
{
// WebGL specific settings would go here
};
}
buildSettings["platform_settings"] = platformSettings;
return JsonConvert.SerializeObject(buildSettings, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting build settings: {e.Message}";
}
}
#endregion
#region Player Settings
/// <summary>
/// Get detailed information for Player Settings
/// </summary>
public static string GetPlayerSettings()
{
try
{
var playerSettings = new Dictionary<string, object>();
// Basic information
playerSettings["company_name"] = PlayerSettings.companyName;
playerSettings["product_name"] = PlayerSettings.productName;
playerSettings["version"] = PlayerSettings.bundleVersion;
playerSettings["bundle_identifier"] = PlayerSettings.GetApplicationIdentifier(EditorUserBuildSettings.selectedBuildTargetGroup);
// Icon & Splash
// Default icon handling varies by platform
playerSettings["has_default_icon"] = true;
playerSettings["use_animated_autorotation"] = PlayerSettings.useAnimatedAutorotation;
// Resolution & Display
var resolutionSettings = new Dictionary<string, object>
{
["default_is_fullscreen"] = PlayerSettings.defaultIsNativeResolution,
["default_screen_width"] = PlayerSettings.defaultScreenWidth,
["default_screen_height"] = PlayerSettings.defaultScreenHeight,
["run_in_background"] = PlayerSettings.runInBackground,
["capture_single_screen"] = PlayerSettings.captureSingleScreen,
// Display resolution dialog removed in newer Unity versions
["use_player_log"] = PlayerSettings.usePlayerLog,
["resize_with_window"] = PlayerSettings.resizableWindow,
["visible_in_background"] = PlayerSettings.visibleInBackground
};
playerSettings["resolution_presentation"] = resolutionSettings;
// Splash Screen
var splashSettings = new Dictionary<string, object>
{
["show_unity_logo"] = PlayerSettings.SplashScreen.showUnityLogo,
["animation_mode"] = PlayerSettings.SplashScreen.animationMode.ToString(),
["background_color"] = ColorToHex(PlayerSettings.SplashScreen.backgroundColor),
["logo_style"] = PlayerSettings.SplashScreen.unityLogoStyle.ToString()
};
playerSettings["splash_screen"] = splashSettings;
// XR Settings
var xrSettings = new Dictionary<string, object>
{
// VR support moved to XR Management package
};
playerSettings["xr_settings"] = xrSettings;
// Publishing Settings
var publishingSettings = new Dictionary<string, object>
{
["use_mac_app_store_validation"] = PlayerSettings.useMacAppStoreValidation,
// Mac App Store category setting
};
playerSettings["publishing_settings"] = publishingSettings;
// Configuration
var configurationSettings = new Dictionary<string, object>
{
["scripting_backend"] = PlayerSettings.GetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup).ToString(),
["api_compatibility_level"] = PlayerSettings.GetApiCompatibilityLevel(EditorUserBuildSettings.selectedBuildTargetGroup).ToString(),
// Input handling setting varies by Unity version
["il2cpp_compiler_configuration"] = PlayerSettings.GetIl2CppCompilerConfiguration(EditorUserBuildSettings.selectedBuildTargetGroup).ToString()
};
playerSettings["configuration"] = configurationSettings;
// Platform-specific settings
var platformSpecific = GetPlatformSpecificPlayerSettings();
playerSettings["platform_specific"] = platformSpecific;
return JsonConvert.SerializeObject(playerSettings, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting player settings: {e.Message}";
}
}
private static Dictionary<string, object> GetPlatformSpecificPlayerSettings()
{
var platformSettings = new Dictionary<string, object>();
// Android-specific settings
var androidSettings = new Dictionary<string, object>
{
["bundle_version_code"] = PlayerSettings.Android.bundleVersionCode,
["min_sdk_version"] = PlayerSettings.Android.minSdkVersion.ToString(),
["target_sdk_version"] = PlayerSettings.Android.targetSdkVersion.ToString(),
["preferred_install_location"] = PlayerSettings.Android.preferredInstallLocation.ToString(),
["force_internet_permission"] = PlayerSettings.Android.forceInternetPermission,
["force_sd_card_permission"] = PlayerSettings.Android.forceSDCardPermission,
["keystore_name"] = PlayerSettings.Android.keystoreName,
["keystore_pass"] = "[PROTECTED]",
["keyalias_name"] = PlayerSettings.Android.keyaliasName,
["use_custom_keystore"] = PlayerSettings.Android.useCustomKeystore
};
platformSettings["android"] = androidSettings;
// iOS-specific settings
var iosSettings = new Dictionary<string, object>
{
["build_number"] = PlayerSettings.iOS.buildNumber,
["target_os_version"] = PlayerSettings.iOS.targetOSVersionString,
["camera_usage_description"] = PlayerSettings.iOS.cameraUsageDescription,
["location_usage_description"] = PlayerSettings.iOS.locationUsageDescription,
["microphone_usage_description"] = PlayerSettings.iOS.microphoneUsageDescription,
["requires_persistent_wifi"] = PlayerSettings.iOS.requiresPersistentWiFi,
// Exit on suspend deprecated, use appInBackgroundBehavior instead
["app_in_background_behavior"] = PlayerSettings.iOS.appInBackgroundBehavior.ToString()
};
platformSettings["ios"] = iosSettings;
return platformSettings;
}
#endregion
#region Quality Settings
/// <summary>
/// Get detailed information for Quality Settings
/// </summary>
public static string GetQualitySettings()
{
try
{
var qualitySettings = new Dictionary<string, object>();
// Current quality level
qualitySettings["current_level"] = QualitySettings.GetQualityLevel();
qualitySettings["current_level_name"] = QualitySettings.names[QualitySettings.GetQualityLevel()];
// All quality levels
var levels = new List<Dictionary<string, object>>();
string[] names = QualitySettings.names;
for (int i = 0; i < names.Length; i++)
{
// Temporarily switch level to retrieve settings
int currentLevel = QualitySettings.GetQualityLevel();
QualitySettings.SetQualityLevel(i, false);
var levelSettings = new Dictionary<string, object>
{
["index"] = i,
["name"] = names[i],
["pixel_light_count"] = QualitySettings.pixelLightCount,
#if UNITY_2022_2_OR_NEWER
["texture_quality"] = QualitySettings.globalTextureMipmapLimit,
#else
["texture_quality"] = QualitySettings.masterTextureLimit,
#endif
["anisotropic_textures"] = QualitySettings.anisotropicFiltering.ToString(),
["anti_aliasing"] = QualitySettings.antiAliasing,
["soft_particles"] = QualitySettings.softParticles,
["realtime_reflection_probes"] = QualitySettings.realtimeReflectionProbes,
["billboard_face_camera_position"] = QualitySettings.billboardsFaceCameraPosition,
["resolution_scaling_fixed_dpi_factor"] = QualitySettings.resolutionScalingFixedDPIFactor,
["texture_streaming_enabled"] = QualitySettings.streamingMipmapsActive,
["texture_streaming_memory_budget"] = QualitySettings.streamingMipmapsMemoryBudget,
["maximum_lod_bias"] = QualitySettings.maximumLODLevel,
["particle_raycast_budget"] = QualitySettings.particleRaycastBudget,
["async_upload_time_slice"] = QualitySettings.asyncUploadTimeSlice,
["async_upload_buffer_size"] = QualitySettings.asyncUploadBufferSize,
["async_upload_persistent_buffer"] = QualitySettings.asyncUploadPersistentBuffer,
["realtime_gi_cpu_usage"] = QualitySettings.realtimeGICPUUsage.ToString(),
["skinned_mesh_max_bone_count"] = QualitySettings.skinWeights.ToString()
};
levels.Add(levelSettings);
// Restore original level
QualitySettings.SetQualityLevel(currentLevel, false);
}
qualitySettings["levels"] = levels;
// Detail settings
var detailSettings = new Dictionary<string, object>
{
["blend_weights"] = QualitySettings.skinWeights.ToString(),
["vsync_count"] = QualitySettings.vSyncCount,
["lod_bias"] = QualitySettings.lodBias,
["maximum_lod_level"] = QualitySettings.maximumLODLevel,
["particle_raycast_budget"] = QualitySettings.particleRaycastBudget,
["soft_vegetation"] = QualitySettings.softVegetation
};
qualitySettings["detail_settings"] = detailSettings;
return JsonConvert.SerializeObject(qualitySettings, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting quality settings: {e.Message}";
}
}
#endregion
#region Input Settings
/// <summary>
/// Get detailed information for Input Settings
/// </summary>
public static string GetInputSettings()
{
try
{
var inputSettings = new Dictionary<string, object>();
// Input system settings
// Input handling setting varies by Unity version
inputSettings["input_system_available"] = true;
// Legacy Input Manager settings
var axes = new List<Dictionary<string, object>>();
// Get Axes settings from Input Manager (using Reflection)
var inputManagerAssets = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/InputManager.asset");
if (inputManagerAssets == null || inputManagerAssets.Length == 0)
{
SynLog.Warn("[GetInputSettings] InputManager asset not found");
inputSettings["input_axes"] = new List<Dictionary<string, object>>();
}
else
{
var inputManagerAsset = inputManagerAssets[0];
if (inputManagerAsset == null)
{
SynLog.Warn("[GetInputSettings] InputManager asset is null");
inputSettings["input_axes"] = new List<Dictionary<string, object>>();
}
else
{
var serializedObject = new SerializedObject(inputManagerAsset);
var axesProperty = serializedObject.FindProperty("m_Axes");
if (axesProperty == null)
{
SynLog.Warn("[GetInputSettings] m_Axes property not found");
inputSettings["input_axes"] = new List<Dictionary<string, object>>();
}
else
{
for (int i = 0; i < axesProperty.arraySize; i++)
{
try
{
var axis = axesProperty.GetArrayElementAtIndex(i);
if (axis != null)
{
var axisData = new Dictionary<string, object>
{
["name"] = axis.FindPropertyRelative("m_Name")?.stringValue ?? "",
["descriptive_name"] = axis.FindPropertyRelative("m_DescriptiveName")?.stringValue ?? "",
["descriptive_negative_name"] = axis.FindPropertyRelative("m_DescriptiveNegativeName")?.stringValue ?? "",
["negative_button"] = axis.FindPropertyRelative("m_NegativeButton")?.stringValue ?? "",
["positive_button"] = axis.FindPropertyRelative("m_PositiveButton")?.stringValue ?? "",
["alt_negative_button"] = axis.FindPropertyRelative("m_AltNegativeButton")?.stringValue ?? "",
["alt_positive_button"] = axis.FindPropertyRelative("m_AltPositiveButton")?.stringValue ?? "",
["gravity"] = axis.FindPropertyRelative("m_Gravity")?.floatValue ?? 0f,
["dead"] = axis.FindPropertyRelative("m_Dead")?.floatValue ?? 0f,
["sensitivity"] = axis.FindPropertyRelative("m_Sensitivity")?.floatValue ?? 1f,
["snap"] = axis.FindPropertyRelative("m_Snap")?.boolValue ?? false,
["invert"] = axis.FindPropertyRelative("m_Invert")?.boolValue ?? false,
["type"] = axis.FindPropertyRelative("m_Type")?.intValue ?? 0,
["axis"] = axis.FindPropertyRelative("m_Axis")?.intValue ?? 0,
["joy_num"] = axis.FindPropertyRelative("m_JoyNum")?.intValue ?? 0
};
axes.Add(axisData);
}
}
catch (Exception axisEx)
{
SynLog.Warn($"[GetInputSettings] Failed to read axis {i}: {axisEx.Message}");
}
}
inputSettings["input_axes"] = axes;
}
}
}
// New Input System information (if available)
#if UNITY_INPUT_SYSTEM_MODULE_ENABLED
try
{
inputSettings["new_input_system"] = new Dictionary<string, object>
{
["enabled"] = true,
["version"] = UnityEngine.InputSystem.InputSystem.version
};
}
catch
{
inputSettings["new_input_system"] = new Dictionary<string, object>
{
["enabled"] = false,
["error"] = "Input System package not available"
};
}
#else
inputSettings["new_input_system"] = new Dictionary<string, object>
{
["enabled"] = false,
["note"] = "Input System module not enabled"
};
#endif
return JsonConvert.SerializeObject(inputSettings, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting input settings: {e.Message}";
}
}
#endregion
#region Physics Settings
/// <summary>
/// Get detailed information for Physics Settings
/// </summary>
public static string GetPhysicsSettings()
{
try
{
var physicsSettings = new Dictionary<string, object>();
// Basic physics settings
physicsSettings["gravity"] = new Dictionary<string, object>
{
["x"] = Physics.gravity.x,
["y"] = Physics.gravity.y,
["z"] = Physics.gravity.z
};
// Default physics material varies by Unity version
physicsSettings["has_default_material"] = true;
physicsSettings["bounce_threshold"] = Physics.bounceThreshold;
physicsSettings["sleep_threshold"] = Physics.sleepThreshold;
physicsSettings["default_contact_offset"] = Physics.defaultContactOffset;
physicsSettings["default_solver_iterations"] = Physics.defaultSolverIterations;
physicsSettings["default_solver_velocity_iterations"] = Physics.defaultSolverVelocityIterations;
// Query settings
physicsSettings["queries_hit_backfaces"] = Physics.queriesHitBackfaces;
physicsSettings["queries_hit_triggers"] = Physics.queriesHitTriggers;
physicsSettings["auto_sync_transforms"] = Physics.autoSyncTransforms;
physicsSettings["reuse_collision_callbacks"] = Physics.reuseCollisionCallbacks;
// Layer collision matrix
var layerCollisionMatrix = new Dictionary<string, object>();
for (int i = 0; i < 32; i++)
{
var layerName = LayerMask.LayerToName(i);
if (!string.IsNullOrEmpty(layerName))
{
var collisions = new List<string>();
for (int j = 0; j < 32; j++)
{
if (!Physics.GetIgnoreLayerCollision(i, j))
{
var otherLayerName = LayerMask.LayerToName(j);
if (!string.IsNullOrEmpty(otherLayerName))
{
collisions.Add(otherLayerName);
}
}
}
layerCollisionMatrix[layerName] = collisions;
}
}
physicsSettings["layer_collision_matrix"] = layerCollisionMatrix;
// 2D Physics settings
var physics2DSettings = new Dictionary<string, object>
{
["gravity"] = new Dictionary<string, object>
{
["x"] = Physics2D.gravity.x,
["y"] = Physics2D.gravity.y
},
// Default physics material varies by Unity version
["has_default_material"] = true,
["velocity_iterations"] = Physics2D.velocityIterations,
["position_iterations"] = Physics2D.positionIterations,
["velocity_threshold"] = Physics2D.bounceThreshold,
["max_linear_correction"] = Physics2D.maxLinearCorrection,
["max_angular_correction"] = Physics2D.maxAngularCorrection,
["max_translation_speed"] = Physics2D.maxTranslationSpeed,
["max_rotation_speed"] = Physics2D.maxRotationSpeed,
["baumgarte_scale"] = Physics2D.baumgarteScale,
["baumgarte_time_of_impact_scale"] = Physics2D.baumgarteTOIScale,
["time_to_sleep"] = Physics2D.timeToSleep,
["linear_sleep_tolerance"] = Physics2D.linearSleepTolerance,
["angular_sleep_tolerance"] = Physics2D.angularSleepTolerance,
["auto_sync_transforms"] = Physics2D.autoSyncTransforms,
["reuse_collision_callbacks"] = Physics2D.reuseCollisionCallbacks,
// Auto simulation deprecated, use simulationMode instead
["queries_hit_triggers"] = Physics2D.queriesHitTriggers,
["queries_start_in_colliders"] = Physics2D.queriesStartInColliders,
["callbacks_on_disable"] = Physics2D.callbacksOnDisable
};
physicsSettings["physics_2d"] = physics2DSettings;
return JsonConvert.SerializeObject(physicsSettings, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting physics settings: {e.Message}";
}
}
#endregion
#region Utility Methods
private static string ColorToHex(Color color)
{
return $"#{ColorUtility.ToHtmlStringRGBA(color)}";
}
/// <summary>
/// Get summary of all project settings
/// </summary>
public static string GetProjectSettingsSummary()
{
try
{
var summary = new Dictionary<string, object>
{
["project_info"] = new Dictionary<string, object>
{
["product_name"] = PlayerSettings.productName,
["bundle_version"] = PlayerSettings.bundleVersion,
["company_name"] = PlayerSettings.companyName,
["bundle_id"] = PlayerSettings.GetApplicationIdentifier(EditorUserBuildSettings.selectedBuildTargetGroup)
},
["build_settings"] = new Dictionary<string, object>
{
["target_platform"] = EditorUserBuildSettings.activeBuildTarget.ToString(),
["development_build"] = EditorUserBuildSettings.development,
["scripting_backend"] = PlayerSettings.GetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup).ToString()
},
["quality_settings"] = new Dictionary<string, object>
{
["quality_level"] = QualitySettings.names[QualitySettings.GetQualityLevel()],
["vsync_count"] = QualitySettings.vSyncCount,
["anti_aliasing"] = QualitySettings.antiAliasing,
["aniso_filtering"] = QualitySettings.anisotropicFiltering.ToString()
},
["physics_settings"] = new Dictionary<string, object>
{
["gravity_3d"] = new Dictionary<string, float>
{
["x"] = Physics.gravity.x,
["y"] = Physics.gravity.y,
["z"] = Physics.gravity.z
},
["gravity_2d"] = new Dictionary<string, float>
{
["x"] = Physics2D.gravity.x,
["y"] = Physics2D.gravity.y
}
},
["statistics"] = GetProjectStatistics()
};
return JsonConvert.SerializeObject(summary, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting project settings summary: {e.Message}";
}
}
/// <summary>
/// Get project statistics information
/// </summary>
private static Dictionary<string, object> GetProjectStatistics()
{
var stats = new Dictionary<string, object>();
try
{
// Number of GameObjects (active scene only)
var rootObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
int totalGameObjects = 0;
foreach (var root in rootObjects)
{
totalGameObjects += CountGameObjectsRecursive(root.transform);
}
// Asset statistics
string[] allAssets = AssetDatabase.GetAllAssetPaths();
var assetTypes = new Dictionary<string, int>();
foreach (string assetPath in allAssets)
{
if (assetPath.StartsWith("Assets/"))
{
string extension = System.IO.Path.GetExtension(assetPath).ToLower();
if (!string.IsNullOrEmpty(extension))
{
if (assetTypes.ContainsKey(extension))
assetTypes[extension]++;
else
assetTypes[extension] = 1;
}
}
}
stats["gameobject_count"] = totalGameObjects;
stats["script_count"] = assetTypes.ContainsKey(".cs") ? assetTypes[".cs"] : 0;
stats["total_assets"] = allAssets.Where(p => p.StartsWith("Assets/")).Count();
stats["asset_types"] = assetTypes;
stats["memory_usage"] = new Dictionary<string, object>
{
["allocated_memory"] = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong(),
["reserved_memory"] = UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong(),
["mono_heap_size"] = UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong(),
["mono_used_size"] = UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong()
};
}
catch (Exception e)
{
stats["error"] = e.Message;
}
return stats;
}
/// <summary>
/// Count GameObjects recursively
/// </summary>
private static int CountGameObjectsRecursive(Transform transform)
{
int count = 1; // Self
for (int i = 0; i < transform.childCount; i++)
{
count += CountGameObjectsRecursive(transform.GetChild(i));
}
return count;
}
#endregion
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 35ea6fbe798d849429bcb67fff074834
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusProjectSettings.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 45faed9ddab504d1296232dba2fc21f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusSetupWindow.cs
uploadId: 920982
@@ -1,910 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.IO;
using Newtonsoft.Json;
namespace SynapticPro
{
/// <summary>
/// Unity project state inspection and reporting class
/// Enables AI to understand current implementation status
/// </summary>
public static class NexusStateInspector
{
/// <summary>
/// Get scene information
/// </summary>
public static string GetSceneInformation(bool includeHierarchy = true, int maxDepth = 3)
{
var scene = SceneManager.GetActiveScene();
var info = new System.Text.StringBuilder();
info.AppendLine($"🎬 Scene Information");
info.AppendLine($"Name: {scene.name}");
info.AppendLine($"Path: {scene.path}");
info.AppendLine($"Build Index: {scene.buildIndex}");
var rootObjects = scene.GetRootGameObjects();
info.AppendLine($"Root GameObject Count: {rootObjects.Length}");
if (includeHierarchy)
{
info.AppendLine("\n📊 Hierarchy Structure:");
foreach (var root in rootObjects)
{
info.Append(GetGameObjectHierarchy(root, 0, maxDepth));
}
}
return info.ToString();
}
/// <summary>
/// Get GameObject details
/// </summary>
public static string GetGameObjectDetails(string name)
{
var obj = GameObject.Find(name);
if (obj == null) return $"❌ GameObject '{name}' not found";
var info = new System.Text.StringBuilder();
info.AppendLine($"🎯 GameObject Details: {obj.name}");
info.AppendLine($"Path: {GetFullPath(obj)}");
info.AppendLine($"Tag: {obj.tag}");
info.AppendLine($"Layer: {LayerMask.LayerToName(obj.layer)} ({obj.layer})");
info.AppendLine($"Active: {obj.activeSelf}");
info.AppendLine($"Static: {obj.isStatic}");
var transform = obj.transform;
info.AppendLine($"\n📐 Transform:");
info.AppendLine($" Position: {transform.position}");
info.AppendLine($" Rotation: {transform.rotation.eulerAngles}");
info.AppendLine($" Scale: {transform.localScale}");
info.AppendLine($" Child Count: {transform.childCount}");
info.AppendLine($"\n🔧 Components:");
var components = obj.GetComponents<Component>();
foreach (var comp in components)
{
if (comp == null) continue;
var compType = comp.GetType();
info.AppendLine($" • {compType.Name}");
// Details of major components
if (comp is Rigidbody rb)
{
info.AppendLine($" - Mass: {rb.mass}");
info.AppendLine($" - Use Gravity: {rb.useGravity}");
info.AppendLine($" - Kinematic: {rb.isKinematic}");
}
else if (comp is Collider col)
{
info.AppendLine($" - Trigger: {col.isTrigger}");
info.AppendLine($" - Enabled: {col.enabled}");
}
else if (comp is Renderer rend)
{
info.AppendLine($" - Material Count: {rend.sharedMaterials.Length}");
info.AppendLine($" - Enabled: {rend.enabled}");
}
}
return info.ToString();
}
/// <summary>
/// Get project asset information
/// </summary>
public static string GetProjectAssets(string assetType = "all", string folder = "Assets")
{
var info = new System.Text.StringBuilder();
info.AppendLine($"📁 Asset Information ({folder})");
string searchFilter = assetType switch
{
"scripts" => "t:MonoScript",
"prefabs" => "t:Prefab",
"materials" => "t:Material",
"textures" => "t:Texture2D",
"audio" => "t:AudioClip",
_ => ""
};
string[] guids = AssetDatabase.FindAssets(searchFilter, new[] { folder });
var assetsByType = new Dictionary<string, List<string>>();
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var type = AssetDatabase.GetMainAssetTypeAtPath(path);
string typeName = type?.Name ?? "Unknown";
if (!assetsByType.ContainsKey(typeName))
assetsByType[typeName] = new List<string>();
assetsByType[typeName].Add(Path.GetFileName(path));
}
foreach (var kvp in assetsByType.OrderBy(k => k.Key))
{
info.AppendLine($"\n{kvp.Key} ({kvp.Value.Count} items):");
foreach (var asset in kvp.Value.Take(10))
{
info.AppendLine($" - {asset}");
}
if (kvp.Value.Count > 10)
{
info.AppendLine($" ... {kvp.Value.Count - 10} more");
}
}
return info.ToString();
}
/// <summary>
/// Get overall project statistics
/// </summary>
public static string GetProjectStatistics()
{
try
{
// Scene statistics
var scene = SceneManager.GetActiveScene();
var allGameObjects = GameObject.FindObjectsOfType<GameObject>();
// Component statistics
var componentCounts = new Dictionary<string, int>();
foreach (var go in allGameObjects)
{
foreach (var comp in go.GetComponents<Component>())
{
if (comp == null) continue;
string typeName = comp.GetType().Name;
componentCounts[typeName] = componentCounts.GetValueOrDefault(typeName, 0) + 1;
}
}
// Asset statistics
string[] allAssets = AssetDatabase.FindAssets("");
var scripts = allAssets.Count(g => AssetDatabase.GUIDToAssetPath(g).EndsWith(".cs"));
var prefabs = allAssets.Count(g => AssetDatabase.GUIDToAssetPath(g).EndsWith(".prefab"));
var materials = allAssets.Count(g => AssetDatabase.GUIDToAssetPath(g).EndsWith(".mat"));
var textures = allAssets.Count(g => AssetDatabase.GUIDToAssetPath(g).EndsWith(".png") ||
AssetDatabase.GUIDToAssetPath(g).EndsWith(".jpg"));
// Nexus-created objects
var nexusCreated = allGameObjects.Where(go => go.name.Contains("Nexus_") ||
go.name.Contains("HelloWorld") ||
go.name.Contains("UI")).ToList();
var statistics = new Dictionary<string, object>
{
["scene_info"] = new Dictionary<string, object>
{
["name"] = scene.name,
["path"] = scene.path,
["is_loaded"] = scene.isLoaded,
["is_dirty"] = scene.isDirty
},
["gameobject_statistics"] = new Dictionary<string, object>
{
["total_count"] = allGameObjects.Length,
["active_count"] = allGameObjects.Count(go => go.activeInHierarchy),
["inactive_count"] = allGameObjects.Count(go => !go.activeInHierarchy)
},
["component_statistics"] = componentCounts.OrderByDescending(k => k.Value).Take(10).ToDictionary(k => k.Key, k => k.Value),
["asset_statistics"] = new Dictionary<string, object>
{
["total_assets"] = allAssets.Length,
["scripts"] = scripts,
["prefabs"] = prefabs,
["materials"] = materials,
["textures"] = textures
},
["nexus_created_objects"] = nexusCreated.Take(10).Select(obj => new Dictionary<string, object>
{
["name"] = obj.name,
["type"] = obj.GetType().Name,
["active"] = obj.activeInHierarchy,
["tag"] = obj.tag,
["layer"] = obj.layer
}).ToList()
};
return JsonConvert.SerializeObject(statistics, Formatting.Indented);
}
catch (System.Exception e)
{
return $"Error getting project statistics: {e.Message}";
}
}
/// <summary>
/// Get camera information
/// </summary>
public static string GetCameraInformation()
{
try
{
var cameras = GameObject.FindObjectsOfType<Camera>();
var mainCam = Camera.main;
var cameraInfo = new Dictionary<string, object>
{
["camera_count"] = cameras.Length,
["main_camera"] = mainCam != null ? mainCam.name : null,
["cameras"] = cameras.Select(cam => new Dictionary<string, object>
{
["name"] = cam.name,
["enabled"] = cam.enabled,
["transform"] = new Dictionary<string, object>
{
["position"] = new Dictionary<string, float>
{
["x"] = cam.transform.position.x,
["y"] = cam.transform.position.y,
["z"] = cam.transform.position.z
},
["rotation"] = new Dictionary<string, float>
{
["x"] = cam.transform.rotation.eulerAngles.x,
["y"] = cam.transform.rotation.eulerAngles.y,
["z"] = cam.transform.rotation.eulerAngles.z
}
},
["camera_settings"] = new Dictionary<string, object>
{
["field_of_view"] = cam.fieldOfView,
["orthographic"] = cam.orthographic,
["orthographic_size"] = cam.orthographic ? cam.orthographicSize : null,
["depth"] = cam.depth,
["culling_mask"] = cam.cullingMask,
["background_color"] = new Dictionary<string, float>
{
["r"] = cam.backgroundColor.r,
["g"] = cam.backgroundColor.g,
["b"] = cam.backgroundColor.b,
["a"] = cam.backgroundColor.a
},
["clear_flags"] = cam.clearFlags.ToString(),
["near_clip_plane"] = cam.nearClipPlane,
["far_clip_plane"] = cam.farClipPlane
},
["render_settings"] = new Dictionary<string, object>
{
["render_texture"] = cam.targetTexture != null ? cam.targetTexture.name : null,
["allow_hdr"] = cam.allowHDR,
["allow_msaa"] = cam.allowMSAA,
["use_physical_properties"] = cam.usePhysicalProperties
},
["has_post_process"] = cam.GetComponent("PostProcessVolume") != null
}).ToList()
};
return JsonConvert.SerializeObject(cameraInfo, Formatting.Indented);
}
catch (System.Exception e)
{
return $"Error getting camera information: {e.Message}";
}
}
/// <summary>
/// Get terrain information
/// </summary>
public static string GetTerrainInformation()
{
try
{
var terrains = GameObject.FindObjectsOfType<Terrain>();
var terrainInfo = new Dictionary<string, object>
{
["terrain_count"] = terrains.Length,
["terrains"] = terrains.Select(terrain => {
var data = terrain.terrainData;
return new Dictionary<string, object>
{
["name"] = terrain.name,
["position"] = new Dictionary<string, float>
{
["x"] = terrain.transform.position.x,
["y"] = terrain.transform.position.y,
["z"] = terrain.transform.position.z
},
["terrain_data"] = new Dictionary<string, object>
{
["size"] = new Dictionary<string, float>
{
["x"] = data.size.x,
["y"] = data.size.y,
["z"] = data.size.z
},
["heightmap_resolution"] = data.heightmapResolution,
["detail_resolution"] = data.detailResolution,
["alphamap_resolution"] = data.alphamapResolution,
["base_map_resolution"] = data.baseMapResolution
},
["terrain_layers"] = new Dictionary<string, object>
{
["count"] = data.terrainLayers?.Length ?? 0,
["layers"] = data.terrainLayers != null ?
data.terrainLayers.Take(5).Where(layer => layer != null).Select(layer => new Dictionary<string, object>
{
["name"] = layer.name,
["diffuse_texture"] = layer.diffuseTexture?.name,
["normal_map"] = layer.normalMapTexture?.name,
["tile_size"] = new Dictionary<string, float>
{
["x"] = layer.tileSize.x,
["y"] = layer.tileSize.y
}
}).ToList() : new List<Dictionary<string, object>>()
},
["vegetation"] = new Dictionary<string, object>
{
["tree_prototype_count"] = data.treePrototypes?.Length ?? 0,
["detail_prototype_count"] = data.detailPrototypes?.Length ?? 0,
["tree_instances"] = data.treeInstanceCount
},
["settings"] = new Dictionary<string, object>
{
["pixel_error"] = terrain.heightmapPixelError,
["base_map_distance"] = terrain.basemapDistance,
["detail_object_distance"] = terrain.detailObjectDistance,
["tree_distance"] = terrain.treeDistance,
["tree_billboard_distance"] = terrain.treeBillboardDistance
}
};
}).ToList()
};
return JsonConvert.SerializeObject(terrainInfo, Formatting.Indented);
}
catch (System.Exception e)
{
return $"Error getting terrain information: {e.Message}";
}
}
/// <summary>
/// Get lighting information
/// </summary>
public static string GetLightingInformation()
{
try
{
var lights = GameObject.FindObjectsOfType<Light>();
var probes = GameObject.FindObjectsOfType<ReflectionProbe>();
var lightingInfo = new Dictionary<string, object>
{
["ambient_settings"] = new Dictionary<string, object>
{
["mode"] = RenderSettings.ambientMode.ToString(),
["intensity"] = RenderSettings.ambientIntensity,
["color"] = new Dictionary<string, float>
{
["r"] = RenderSettings.ambientLight.r,
["g"] = RenderSettings.ambientLight.g,
["b"] = RenderSettings.ambientLight.b,
["a"] = RenderSettings.ambientLight.a
},
["skybox"] = RenderSettings.skybox != null ? RenderSettings.skybox.name : null
},
["fog_settings"] = new Dictionary<string, object>
{
["enabled"] = RenderSettings.fog,
["mode"] = RenderSettings.fogMode.ToString(),
["color"] = new Dictionary<string, float>
{
["r"] = RenderSettings.fogColor.r,
["g"] = RenderSettings.fogColor.g,
["b"] = RenderSettings.fogColor.b,
["a"] = RenderSettings.fogColor.a
},
["density"] = RenderSettings.fogDensity,
["start_distance"] = RenderSettings.fogStartDistance,
["end_distance"] = RenderSettings.fogEndDistance
},
["lights"] = new Dictionary<string, object>
{
["count"] = lights.Length,
["light_list"] = lights.Select(light => new Dictionary<string, object>
{
["name"] = light.name,
["enabled"] = light.enabled,
["type"] = light.type.ToString(),
["color"] = new Dictionary<string, float>
{
["r"] = light.color.r,
["g"] = light.color.g,
["b"] = light.color.b,
["a"] = light.color.a
},
["intensity"] = light.intensity,
["range"] = light.range,
["spot_angle"] = light.spotAngle,
["shadows"] = light.shadows.ToString(),
["transform"] = light.type == LightType.Directional ?
new Dictionary<string, object>
{
["rotation"] = new Dictionary<string, float>
{
["x"] = light.transform.rotation.eulerAngles.x,
["y"] = light.transform.rotation.eulerAngles.y,
["z"] = light.transform.rotation.eulerAngles.z
}
} :
new Dictionary<string, object>
{
["position"] = new Dictionary<string, float>
{
["x"] = light.transform.position.x,
["y"] = light.transform.position.y,
["z"] = light.transform.position.z
}
}
}).ToList()
},
["reflection_probes"] = new Dictionary<string, object>
{
["count"] = probes.Length,
["probes"] = probes.Select(probe => new Dictionary<string, object>
{
["name"] = probe.name,
["enabled"] = probe.enabled,
["resolution"] = probe.resolution,
["hdr"] = probe.hdr,
["clear_flags"] = probe.clearFlags.ToString(),
["culling_mask"] = probe.cullingMask
}).ToList()
}
};
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.SerializeObject(lightingInfo, Formatting.Indented, settings);
}
catch (System.Exception e)
{
return $"Error getting lighting information: {e.Message}";
}
}
/// <summary>
/// Get material information
/// </summary>
public static string GetMaterialInformation()
{
try
{
// Materials used in scene
var renderers = GameObject.FindObjectsOfType<Renderer>();
var usedMaterials = new HashSet<Material>();
foreach (var renderer in renderers)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat != null)
{
usedMaterials.Add(mat);
}
}
}
// Classify by shader
var materialsByShader = usedMaterials.GroupBy(m => m.shader.name);
// Material assets in project
var materialAssets = AssetDatabase.FindAssets("t:Material");
var materialInfo = new Dictionary<string, object>
{
["scene_materials"] = new Dictionary<string, object>
{
["total_count"] = usedMaterials.Count,
["by_shader"] = materialsByShader.Select(group => new Dictionary<string, object>
{
["shader_name"] = group.Key,
["count"] = group.Count(),
["materials"] = group.Take(5).Select(mat =>
{
var matData = new Dictionary<string, object>
{
["name"] = mat.name,
["shader"] = mat.shader.name,
["render_queue"] = mat.renderQueue,
["keywords"] = mat.shaderKeywords.ToList()
};
// Color properties
if (mat.HasProperty("_Color") || mat.HasProperty("_BaseColor"))
{
var color = mat.HasProperty("_Color") ? mat.GetColor("_Color") : mat.GetColor("_BaseColor");
matData["color"] = new Dictionary<string, float>
{
["r"] = color.r,
["g"] = color.g,
["b"] = color.b,
["a"] = color.a
};
}
// Texture properties
if (mat.HasProperty("_MainTex") || mat.HasProperty("_BaseMap"))
{
var tex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : mat.GetTexture("_BaseMap");
if (tex != null)
{
matData["main_texture"] = new Dictionary<string, object>
{
["name"] = tex.name,
["width"] = tex.width,
["height"] = tex.height
};
}
}
// PBR properties
var pbrData = new Dictionary<string, object>();
if (mat.HasProperty("_Metallic"))
{
pbrData["metallic"] = mat.GetFloat("_Metallic");
}
if (mat.HasProperty("_Glossiness"))
{
pbrData["smoothness"] = mat.GetFloat("_Glossiness");
}
else if (mat.HasProperty("_Smoothness"))
{
pbrData["smoothness"] = mat.GetFloat("_Smoothness");
}
if (mat.HasProperty("_BumpMap") || mat.HasProperty("_NormalMap"))
{
var normalMap = mat.HasProperty("_BumpMap") ? mat.GetTexture("_BumpMap") : mat.GetTexture("_NormalMap");
pbrData["has_normal_map"] = normalMap != null;
}
if (pbrData.Count > 0)
{
matData["pbr_properties"] = pbrData;
}
return matData;
}).ToList()
}).ToList()
},
["project_materials"] = new Dictionary<string, object>
{
["total_count"] = materialAssets.Length,
["paths"] = materialAssets.Take(20).Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToList()
}
};
return JsonConvert.SerializeObject(materialInfo, Formatting.Indented);
}
catch (System.Exception e)
{
return $"Error getting material information: {e.Message}";
}
}
/// <summary>
/// Get UI information
/// </summary>
public static string GetUIInformation()
{
#if UNITY_EDITOR
try
{
var canvases = GameObject.FindObjectsOfType<Canvas>();
var eventSystem = GameObject.FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
var uiInfo = new Dictionary<string, object>
{
["canvas_count"] = canvases.Length,
["canvases"] = canvases.Select(canvas =>
{
// Aggregate UI elements
var buttons = canvas.GetComponentsInChildren<UnityEngine.UI.Button>(true);
var texts = canvas.GetComponentsInChildren<UnityEngine.UI.Text>(true);
var images = canvas.GetComponentsInChildren<UnityEngine.UI.Image>(true);
var inputFields = canvas.GetComponentsInChildren<UnityEngine.UI.InputField>(true);
var sliders = canvas.GetComponentsInChildren<UnityEngine.UI.Slider>(true);
var toggles = canvas.GetComponentsInChildren<UnityEngine.UI.Toggle>(true);
var scrollViews = canvas.GetComponentsInChildren<UnityEngine.UI.ScrollRect>(true);
return new Dictionary<string, object>
{
["name"] = canvas.name,
["enabled"] = canvas.enabled,
["render_mode"] = canvas.renderMode.ToString(),
["sorting_order"] = canvas.sortingOrder,
["sorting_layer"] = canvas.sortingLayerName,
["pixel_perfect"] = canvas.pixelPerfect,
["ui_elements"] = new Dictionary<string, object>
{
["buttons"] = new Dictionary<string, object>
{
["count"] = buttons.Length,
["items"] = buttons.Take(5).Select(btn => {
var btnText = btn.GetComponentInChildren<UnityEngine.UI.Text>();
return new Dictionary<string, object>
{
["name"] = btn.name,
["text"] = btnText != null ? btnText.text : null,
["interactable"] = btn.interactable,
["active"] = btn.gameObject.activeInHierarchy
};
}).ToList()
},
["texts"] = new Dictionary<string, object>
{
["count"] = texts.Length,
["items"] = texts.Take(5).Select(txt => new Dictionary<string, object>
{
["name"] = txt.name,
["text"] = txt.text,
["font"] = txt.font != null ? txt.font.name : null,
["font_size"] = txt.fontSize,
["color"] = new Dictionary<string, float>
{
["r"] = txt.color.r,
["g"] = txt.color.g,
["b"] = txt.color.b,
["a"] = txt.color.a
}
}).ToList()
},
["images"] = new Dictionary<string, object>
{
["count"] = images.Length,
["items"] = images.Take(5).Select(img => new Dictionary<string, object>
{
["name"] = img.name,
["sprite_name"] = img.sprite != null ? img.sprite.name : "(none)",
["color"] = new Dictionary<string, float>
{
["r"] = img.color.r,
["g"] = img.color.g,
["b"] = img.color.b,
["a"] = img.color.a
},
["raycast_target"] = img.raycastTarget,
["active"] = img.gameObject.activeInHierarchy
}).ToList()
},
["input_fields"] = inputFields.Length,
["sliders"] = sliders.Length,
["toggles"] = toggles.Length,
["scroll_views"] = scrollViews.Length
}
};
}).ToList(),
["event_system"] = eventSystem != null ? new Dictionary<string, object>
{
["name"] = eventSystem.name,
["current_selected"] = eventSystem.currentSelectedGameObject != null ? eventSystem.currentSelectedGameObject.name : null,
["send_navigation_events"] = eventSystem.sendNavigationEvents,
["pixel_drag_threshold"] = eventSystem.pixelDragThreshold
} : null
};
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.SerializeObject(uiInfo, Formatting.Indented, settings);
}
catch (System.Exception e)
{
return $"Error getting UI information: {e.Message}";
}
#else
return "UI information is only available in Unity Editor";
#endif
}
/// <summary>
/// Get physics information
/// </summary>
public static string GetPhysicsInformation()
{
try
{
var rigidbodies = GameObject.FindObjectsOfType<Rigidbody>();
var colliders = GameObject.FindObjectsOfType<Collider>();
var joints = GameObject.FindObjectsOfType<Joint>();
var physicsInfo = new Dictionary<string, object>
{
["global_settings"] = new Dictionary<string, object>
{
["gravity"] = new Dictionary<string, float>
{
["x"] = Physics.gravity.x,
["y"] = Physics.gravity.y,
["z"] = Physics.gravity.z
},
["default_solver_iterations"] = Physics.defaultSolverIterations,
["default_solver_velocity_iterations"] = Physics.defaultSolverVelocityIterations,
["bounce_threshold"] = Physics.bounceThreshold,
["sleep_threshold"] = Physics.sleepThreshold,
["default_contact_offset"] = Physics.defaultContactOffset,
["queries_hit_triggers"] = Physics.queriesHitTriggers
},
["rigidbodies"] = new Dictionary<string, object>
{
["total_count"] = rigidbodies.Length,
["items"] = rigidbodies.Take(10).Select(rb => new Dictionary<string, object>
{
["name"] = rb.name,
["mass"] = rb.mass,
["drag"] = rb.linearDamping,
["angular_drag"] = rb.angularDamping,
["use_gravity"] = rb.useGravity,
["is_kinematic"] = rb.isKinematic,
["constraints"] = rb.constraints.ToString(),
["velocity"] = new Dictionary<string, object>
{
["magnitude"] = rb.linearVelocity.magnitude,
["vector"] = new Dictionary<string, float>
{
["x"] = rb.linearVelocity.x,
["y"] = rb.linearVelocity.y,
["z"] = rb.linearVelocity.z
}
},
["angular_velocity"] = new Dictionary<string, float>
{
["x"] = rb.angularVelocity.x,
["y"] = rb.angularVelocity.y,
["z"] = rb.angularVelocity.z
}
}).ToList()
},
["colliders"] = new Dictionary<string, object>
{
["total_count"] = colliders.Length,
["by_type"] = colliders.GroupBy(c => c.GetType().Name).Select(group => new Dictionary<string, object>
{
["type"] = group.Key,
["count"] = group.Count()
}).ToList(),
["triggers"] = colliders.Count(c => c.isTrigger),
["non_triggers"] = colliders.Count(c => !c.isTrigger)
},
["joints"] = new Dictionary<string, object>
{
["total_count"] = joints.Length,
["by_type"] = joints.GroupBy(j => j.GetType().Name).Select(group => new Dictionary<string, object>
{
["type"] = group.Key,
["count"] = group.Count()
}).ToList()
}
};
return JsonConvert.SerializeObject(physicsInfo, Formatting.Indented);
}
catch (System.Exception e)
{
return $"Error getting physics information: {e.Message}";
}
}
/// <summary>
/// Check implementation progress
/// </summary>
public static string GetImplementationProgress()
{
var info = new System.Text.StringBuilder();
info.AppendLine("📈 Implementation Progress Checklist");
// Check UI elements
var canvas = GameObject.FindObjectOfType<Canvas>();
info.AppendLine($"\n🖼️ UI Implementation:");
info.AppendLine($" ✓ Canvas: {(canvas != null ? "Present" : "None")}");
if (canvas != null)
{
var buttons = canvas.GetComponentsInChildren<UnityEngine.UI.Button>();
var texts = canvas.GetComponentsInChildren<UnityEngine.UI.Text>();
var images = canvas.GetComponentsInChildren<UnityEngine.UI.Image>();
info.AppendLine($" ✓ Buttons: {buttons.Length}");
info.AppendLine($" ✓ Texts: {texts.Length}");
info.AppendLine($" ✓ Images: {images.Length}");
}
// Check GameObjects
info.AppendLine($"\n🎮 GameObjects:");
var primitives = new[] { "Cube", "Sphere", "Cylinder", "Plane", "Capsule" };
foreach (var prim in primitives)
{
var count = GameObject.FindObjectsOfType<GameObject>()
.Count(go => go.name.Contains(prim));
if (count > 0)
{
info.AppendLine($" ✓ {prim}: {count}");
}
}
// Check script implementation
var customScripts = AssetDatabase.FindAssets("t:MonoScript", new[] { "Assets" })
.Select(g => AssetDatabase.GUIDToAssetPath(g))
.Where(p => !p.Contains("/Editor/") && !p.Contains("Nexus"))
.Select(p => Path.GetFileName(p))
.ToList();
info.AppendLine($"\n📝 Custom Scripts ({customScripts.Count}):");
foreach (var script in customScripts.Take(5))
{
info.AppendLine($" - {script}");
}
return info.ToString();
}
// Helper methods
private static string GetGameObjectHierarchy(GameObject obj, int depth, int maxDepth)
{
if (depth >= maxDepth) return "";
var indent = new string(' ', depth * 2);
var info = new System.Text.StringBuilder();
// Object name and components
info.Append($"{indent}├─ {obj.name}");
var components = obj.GetComponents<Component>()
.Where(c => c != null && !(c is Transform))
.Select(c => c.GetType().Name);
if (components.Any())
{
info.Append($" [{string.Join(", ", components)}]");
}
if (!obj.activeInHierarchy)
{
info.Append(" (Inactive)");
}
info.AppendLine();
// Child objects
foreach (Transform child in obj.transform)
{
info.Append(GetGameObjectHierarchy(child.gameObject, depth + 1, maxDepth));
}
return info.ToString();
}
private static string GetFullPath(GameObject obj)
{
var path = obj.name;
var parent = obj.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 9322b6d5d3a284616bffe8a0ce6bc417
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusStateInspector.cs
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 3ff7e139f4872445a94af71f9793b9a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusVFXBuilder.cs
uploadId: 920982
@@ -1,73 +0,0 @@
using System;
using System.IO;
using UnityEngine;
using SynapticAIPro;
namespace SynapticAIPro
{
/// <summary>
/// Centralized version management - reads from package.json
/// </summary>
public static class NexusVersion
{
private static string _cachedVersion = null;
private static string _packageJsonPath = null;
/// <summary>
/// Get the current version from package.json
/// </summary>
public static string Current
{
get
{
if (_cachedVersion == null)
{
_cachedVersion = ReadVersionFromPackageJson();
}
return _cachedVersion;
}
}
/// <summary>
/// Force refresh the cached version (useful after package update)
/// </summary>
public static void RefreshCache()
{
_cachedVersion = null;
}
private static string ReadVersionFromPackageJson()
{
try
{
if (_packageJsonPath == null)
{
_packageJsonPath = Path.Combine(Application.dataPath, "Synaptic AI Pro/package.json");
}
if (File.Exists(_packageJsonPath))
{
var json = File.ReadAllText(_packageJsonPath);
// Simple parsing - find "version": "x.x.x"
var versionIndex = json.IndexOf("\"version\"");
if (versionIndex >= 0)
{
var colonIndex = json.IndexOf(':', versionIndex);
var firstQuote = json.IndexOf('"', colonIndex);
var secondQuote = json.IndexOf('"', firstQuote + 1);
if (firstQuote >= 0 && secondQuote > firstQuote)
{
return json.Substring(firstQuote + 1, secondQuote - firstQuote - 1);
}
}
}
}
catch (Exception e)
{
SynLog.Warn($"[Synaptic] Failed to read version from package.json: {e.Message}");
}
return "1.0.0"; // Fallback
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 34dd79f80af8a469fb288808a529b914
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusVersion.cs
uploadId: 920982
@@ -1,923 +0,0 @@
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEditor;
using Newtonsoft.Json;
using System.Collections.Generic;
using SynapticAIPro;
namespace SynapticPro
{
/// <summary>
/// WebSocket client for editor mode
/// Manages communication with MCP server
/// </summary>
public class NexusWebSocketClient
{
private ClientWebSocket webSocket;
private CancellationTokenSource cancellationTokenSource;
private bool isConnected = false;
private Queue<string> messageQueue = new Queue<string>();
private bool shouldReconnect = true;
private int reconnectAttempts = 0;
private const int maxReconnectAttempts = 30;
private const int reconnectDelay = 2000; // 2 seconds between attempts
private string serverUrl = "ws://127.0.0.1:8090";
private const int CONNECT_TIMEOUT_SECONDS = 5;
private readonly List<Task> backgroundTasks = new List<Task>();
public bool IsConnected => isConnected;
public event Action<string> OnMessageReceived;
public event Action OnConnected;
public event Action OnDisconnected;
private static NexusWebSocketClient instance;
public static NexusWebSocketClient Instance
{
get
{
if (instance == null)
{
instance = new NexusWebSocketClient();
}
return instance;
}
}
private static void LogTaskFault(Task t, string label)
{
t.ContinueWith(
x => SynLog.Warn($"[Nexus WebSocket] {label} faulted: {x.Exception?.GetBaseException().Message}"),
TaskContinuationOptions.OnlyOnFaulted);
}
public async Task<bool> Connect(string url = "ws://127.0.0.1:8090")
{
shouldReconnect = true;
reconnectAttempts = 0;
while (shouldReconnect && reconnectAttempts < maxReconnectAttempts)
{
try
{
SynLog.Info($"[Nexus WebSocket] Connecting to {url}... (Attempt {reconnectAttempts + 1})");
webSocket = new ClientWebSocket();
cancellationTokenSource = new CancellationTokenSource();
using (var connectCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token))
{
connectCts.CancelAfter(TimeSpan.FromSeconds(CONNECT_TIMEOUT_SECONDS));
await webSocket.ConnectAsync(new Uri(url), connectCts.Token);
}
isConnected = true;
reconnectAttempts = 0; // Reset on success
SynLog.Info("[Nexus WebSocket] Connected successfully!");
OnConnected?.Invoke();
// Start message receive loop (tracked so Disconnect can await)
var receiveTask = Task.Run(async () => await ReceiveLoop());
LogTaskFault(receiveTask, "ReceiveLoop");
backgroundTasks.Add(receiveTask);
// Start heartbeat
var heartbeatTask = Task.Run(async () => await HeartbeatLoop());
LogTaskFault(heartbeatTask, "HeartbeatLoop");
backgroundTasks.Add(heartbeatTask);
// Notify Unity is ready
await SendMessage(new { type = "unity_ready", version = NexusVersion.Current });
return true;
}
catch (Exception e)
{
Debug.LogError($"[Nexus WebSocket] Connection failed: {e.Message}");
isConnected = false;
reconnectAttempts++;
if (reconnectAttempts < maxReconnectAttempts && shouldReconnect)
{
SynLog.Info($"[Nexus WebSocket] Retrying in {reconnectDelay / 1000} seconds...");
await Task.Delay(reconnectDelay);
}
}
}
return false;
}
private async Task ReceiveLoop()
{
var buffer = new byte[4096];
var messageBuilder = new StringBuilder();
try
{
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
cancellationTokenSource.Token
);
if (result.MessageType == WebSocketMessageType.Text)
{
// Accumulate fragments until EndOfMessage. Without this,
// any message larger than 4096B (e.g. tool args / scene
// dumps) gets truncated mid-chunk and fails JSON parse —
// root cause of ESC-0102 (Win + Unity 6.3 MCP receive
// appears to "drop" large messages).
messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
if (result.EndOfMessage)
{
var message = messageBuilder.ToString();
messageBuilder.Clear();
SynLog.Info($"[Nexus WebSocket] Received ({message.Length} chars): {message.Substring(0, System.Math.Min(message.Length, 200))}");
lock (messageQueue)
{
messageQueue.Enqueue(message);
}
}
}
else if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
}
}
catch (Exception e)
{
Debug.LogError($"[Nexus WebSocket] Receive error: {e.Message}");
}
finally
{
isConnected = false;
OnDisconnected?.Invoke();
}
}
public void ProcessMessages()
{
lock (messageQueue)
{
while (messageQueue.Count > 0)
{
var message = messageQueue.Dequeue();
OnMessageReceived?.Invoke(message);
try
{
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(message);
if (data != null && data.ContainsKey("type"))
{
ProcessUnityCommand(data);
}
}
catch (Exception e)
{
Debug.LogError($"[Nexus WebSocket] Message processing error: {e.Message}");
}
}
}
}
public event Action<string, string> OnClaudeResponse; // sessionId, message
public event Action<string> OnChatStatusUpdate; // status message
private void ProcessUnityCommand(Dictionary<string, object> data)
{
var type = data["type"].ToString();
if (type == "unity_operation")
{
var command = data.ContainsKey("command") ? data["command"].ToString() : "";
var parameters = data.ContainsKey("parameters") ? data["parameters"] as Newtonsoft.Json.Linq.JObject : null;
SynLog.Info($"[Nexus WebSocket] Executing Unity command: {command}");
// Execute Unity operation in editor mode
EditorApplication.delayCall += () =>
{
ExecuteUnityOperation(command, parameters);
};
}
else if (type == "claude_response")
{
// Real-time response from Claude Desktop
var responseData = data.ContainsKey("data") ? data["data"] as Newtonsoft.Json.Linq.JObject : null;
if (responseData != null)
{
var message = responseData.Value<string>("message") ?? "";
var sessionId = responseData.Value<string>("sessionId") ?? "";
var responseType = responseData.Value<string>("responseType") ?? "response";
SynLog.Info($"[Nexus WebSocket] Claude response received: {message}");
// Fire event on main thread
EditorApplication.delayCall += () =>
{
OnClaudeResponse?.Invoke(sessionId, message);
};
}
}
else if (type == "chat_initiated")
{
// Chat initiated notification
var chatData = data.ContainsKey("data") ? data["data"] as Newtonsoft.Json.Linq.JObject : null;
if (chatData != null)
{
var status = chatData.Value<string>("status") ?? "Processing...";
SynLog.Info($"[Nexus WebSocket] Chat initiated: {status}");
EditorApplication.delayCall += () =>
{
OnChatStatusUpdate?.Invoke(status);
};
}
}
}
private async void ExecuteUnityOperation(string command, Newtonsoft.Json.Linq.JObject parameters)
{
SynLog.Info($"[Nexus WebSocket] Executing Unity operation: {command}");
SynLog.Info($"[Nexus WebSocket] Parameters: {parameters?.ToString()}");
var operationId = parameters?.Value<string>("operationId") ?? Guid.NewGuid().ToString();
try
{
var operation = new NexusUnityOperation
{
type = ConvertCommandToOperationType(command),
parameters = new Dictionary<string, string>()
};
// Convert parameters
if (parameters != null)
{
foreach (var prop in parameters.Properties())
{
if (prop.Name == "operationId") continue;
var value = prop.Value;
if (value is Newtonsoft.Json.Linq.JObject jObj)
{
// Process nested objects (Vector3, etc.)
if (jObj.ContainsKey("x") && jObj.ContainsKey("y") && jObj.ContainsKey("z"))
{
operation.parameters[prop.Name] = $"{jObj["x"]},{jObj["y"]},{jObj["z"]}";
}
else if (jObj.ContainsKey("x") && jObj.ContainsKey("y"))
{
operation.parameters[prop.Name] = $"{jObj["x"]},{jObj["y"]}";
}
else
{
operation.parameters[prop.Name] = value.ToString();
}
}
else
{
operation.parameters[prop.Name] = value.ToString();
}
}
}
// Execute
string result = "";
bool success = true;
// Process information retrieval commands
switch (operation.type)
{
case "GET_SCENE_INFO":
result = NexusStateInspector.GetSceneInformation();
break;
case "GET_CAMERA_INFO":
result = NexusStateInspector.GetCameraInformation();
break;
case "GET_TERRAIN_INFO":
result = NexusStateInspector.GetTerrainInformation();
break;
case "GET_LIGHTING_INFO":
result = NexusStateInspector.GetLightingInformation();
break;
case "GET_MATERIAL_INFO":
result = NexusStateInspector.GetMaterialInformation();
break;
case "GET_UI_INFO":
result = NexusStateInspector.GetUIInformation();
break;
case "GET_PHYSICS_INFO":
result = NexusStateInspector.GetPhysicsInformation();
break;
case "GET_GAMEOBJECT_DETAILS":
var name = operation.parameters.GetValueOrDefault("name", "");
result = NexusStateInspector.GetGameObjectDetails(name);
break;
case "GET_PROJECT_STATS":
result = NexusStateInspector.GetProjectStatistics();
break;
default:
// Normal operation.
// ESC-0107 fix: previously used `.Result` which blocks the
// main thread (delayCall) on a Task whose continuation may
// need to repost via UnitySynchronizationContext → classic
// SyncContext deadlock. ConfigureAwait(false) drops the
// captured context so the continuation can run on any
// thread; the body of ExecuteOperation is itself sync
// for most cases (e.g. RUN_CSHARP returns immediately).
var executor = new NexusUnityExecutor();
result = await executor.ExecuteOperation(operation).ConfigureAwait(false);
// Error check
if (result.StartsWith("Error") || result.Contains("not found") || result.Contains("failed"))
{
success = false;
}
break;
}
// 既存のMCP通信と同じフォーマットを使用
var response = new Dictionary<string, object>
{
["type"] = "operation_result",
["id"] = operationId,
["content"] = result,
["data"] = new Dictionary<string, object> { ["success"] = success }
};
SynLog.Info($"[Nexus WebSocket] Operation result: {result}");
// Send result to MCP server
await SendMessage(response);
}
catch (Exception e)
{
var errorResponse = new Dictionary<string, object>
{
["type"] = "operation_result",
["id"] = operationId,
["content"] = e.Message,
["data"] = new Dictionary<string, object> { ["success"] = false }
};
Debug.LogError($"[Nexus WebSocket] Operation execution error: {e.Message}\n{e.StackTrace}");
// Send error response to MCP server
await SendMessage(errorResponse);
}
}
private string ConvertCommandToOperationType(string command)
{
switch (command)
{
case "create_ui":
return "CREATE_UI";
case "create_gameobject":
return "CREATE_GAMEOBJECT";
case "instantiate_prefab":
return "INSTANTIATE_PREFAB";
case "set_transform":
return "SET_PROPERTY";
case "setup_camera":
return "SETUP_CAMERA";
case "create_particle_system":
return "CREATE_PARTICLE_SYSTEM";
case "setup_navmesh":
return "SETUP_NAVMESH";
case "create_audio_mixer":
return "CREATE_AUDIO_MIXER";
case "undo":
return "UNDO";
case "redo":
return "REDO";
case "get_history":
return "GET_HISTORY";
// Information retrieval
case "get_scene_info":
return "GET_SCENE_INFO";
case "get_camera_info":
return "GET_CAMERA_INFO";
case "get_terrain_info":
return "GET_TERRAIN_INFO";
case "get_lighting_info":
return "GET_LIGHTING_INFO";
case "get_material_info":
return "GET_MATERIAL_INFO";
case "get_ui_info":
return "GET_UI_INFO";
case "get_physics_info":
return "GET_PHYSICS_INFO";
case "get_gameobject_details":
return "GET_GAMEOBJECT_DETAILS";
case "list_assets":
return "LIST_ASSETS";
case "get_project_stats":
return "GET_PROJECT_STATS";
default:
return command.ToUpper();
}
}
public async Task SendMessage(object data)
{
if (!isConnected || webSocket.State != WebSocketState.Open)
{
SynLog.Warn("[Nexus WebSocket] Cannot send message: not connected");
return;
}
try
{
var json = JsonConvert.SerializeObject(data);
var bytes = Encoding.UTF8.GetBytes(json);
await webSocket.SendAsync(
new ArraySegment<byte>(bytes),
WebSocketMessageType.Text,
true,
cancellationTokenSource.Token
);
}
catch (Exception e)
{
Debug.LogError($"[Nexus WebSocket] Send error: {e.Message}");
}
}
private async Task HeartbeatLoop()
{
while (isConnected && !cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
// Send heartbeat every 30 seconds
await Task.Delay(30000, cancellationTokenSource.Token);
if (webSocket.State == WebSocketState.Open)
{
await SendMessage(new { type = "heartbeat", timestamp = DateTime.Now.Ticks });
}
else
{
SynLog.Warn("[Nexus WebSocket] Connection lost during heartbeat");
isConnected = false;
OnDisconnected?.Invoke();
// Attempt reconnection
if (shouldReconnect)
{
var reconnectTask = Task.Run(async () => await Connect());
LogTaskFault(reconnectTask, "HeartbeatReconnect");
}
break;
}
}
catch (Exception e)
{
if (!cancellationTokenSource.Token.IsCancellationRequested)
{
Debug.LogError($"[Nexus WebSocket] Heartbeat error: {e.Message}");
}
break;
}
}
}
public async Task Disconnect()
{
shouldReconnect = false; // Disable auto-reconnect
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
try
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closing",
cancellationTokenSource.Token
);
}
catch (Exception e)
{
Debug.LogError($"[Nexus WebSocket] Disconnect error: {e.Message}");
}
}
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
isConnected = false;
// Give background tasks a bounded window to unwind before disposing resources.
if (backgroundTasks.Count > 0)
{
try { Task.WhenAll(backgroundTasks).Wait(TimeSpan.FromSeconds(2)); } catch { }
backgroundTasks.Clear();
}
}
/// <summary>
/// Set server URL
/// </summary>
public void SetServerUrl(string url)
{
serverUrl = url;
SynLog.Info($"[Nexus WebSocket] Server URL changed to: {url}");
}
}
/// <summary>
/// Manages WebSocket client updates
/// </summary>
[InitializeOnLoad]
public static class NexusWebSocketUpdater
{
static NexusWebSocketUpdater()
{
EditorApplication.update += Update;
}
private static void Update()
{
NexusWebSocketClient.Instance?.ProcessMessages();
NexusHTTPWebSocketClient.Instance?.ProcessMessages();
}
}
/// <summary>
/// WebSocket client for HTTP Server connection
/// Separate from MCP WebSocket to allow both to run simultaneously
/// </summary>
public class NexusHTTPWebSocketClient
{
private ClientWebSocket webSocket;
private CancellationTokenSource cancellationTokenSource;
private bool isConnected = false;
private Queue<string> messageQueue = new Queue<string>();
private bool shouldReconnect = true;
private int reconnectAttempts = 0;
private const int maxReconnectAttempts = 10;
private const int reconnectDelay = 1000;
private const int CONNECT_TIMEOUT_SECONDS = 5;
private readonly List<Task> backgroundTasks = new List<Task>();
// Reentrancy guard. Connect() can be invoked from three paths (UI button,
// NexusEditorMCPService HTTP auto-connect, and ReceiveLoop's finally auto-reconnect);
// without this gate, concurrent calls clobber `webSocket`/`cancellationTokenSource`
// and produce duplicate "Connecting/Connected/Disconnected" log spam.
private volatile bool connectInFlight = false;
private string serverUrl = "ws://127.0.0.1:8086";
private int port = 8086;
public bool IsConnected => isConnected;
public int Port => port;
private static NexusHTTPWebSocketClient instance;
public static NexusHTTPWebSocketClient Instance
{
get
{
if (instance == null)
{
instance = new NexusHTTPWebSocketClient();
}
return instance;
}
}
private static void LogTaskFault(Task t, string label)
{
t.ContinueWith(
x => SynLog.Warn($"[HTTP WebSocket] {label} faulted: {x.Exception?.GetBaseException().Message}"),
TaskContinuationOptions.OnlyOnFaulted);
}
public async Task<bool> Connect(int httpPort)
{
// Reentrancy guard — silently drop concurrent Connect calls.
if (connectInFlight)
{
return isConnected;
}
if (isConnected && port == httpPort)
{
return true;
}
connectInFlight = true;
try
{
port = httpPort;
serverUrl = $"ws://127.0.0.1:{port}";
shouldReconnect = true;
reconnectAttempts = 0;
while (shouldReconnect && reconnectAttempts < maxReconnectAttempts)
{
try
{
SynLog.Info($"[HTTP WebSocket] Connecting to {serverUrl}... (Attempt {reconnectAttempts + 1})");
var ws = new ClientWebSocket();
ws.Options.SetRequestHeader("X-Client-Type", "unity");
var cts = new CancellationTokenSource();
webSocket = ws;
cancellationTokenSource = cts;
using (var connectCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token))
{
connectCts.CancelAfter(TimeSpan.FromSeconds(CONNECT_TIMEOUT_SECONDS));
await ws.ConnectAsync(new Uri(serverUrl), connectCts.Token);
}
isConnected = true;
reconnectAttempts = 0;
SynLog.Info($"[HTTP WebSocket] Connected to HTTP Server on port {port}!");
// Start message receive loop — snapshot ws+cts so a stale loop whose
// fields were overwritten by a subsequent Connect bails out cleanly.
var receiveTask = Task.Run(async () => await ReceiveLoop(ws, cts));
LogTaskFault(receiveTask, "ReceiveLoop");
backgroundTasks.Add(receiveTask);
// Notify Unity is ready
await SendMessage(new { type = "unity_ready", version = NexusVersion.Current });
return true;
}
catch (Exception e)
{
SynLog.Warn($"[HTTP WebSocket] Connection failed: {e.Message}");
isConnected = false;
reconnectAttempts++;
if (reconnectAttempts < maxReconnectAttempts && shouldReconnect)
{
await Task.Delay(reconnectDelay);
}
}
}
SynLog.Warn($"[HTTP WebSocket] Could not connect after {maxReconnectAttempts} attempts");
return false;
}
finally
{
connectInFlight = false;
}
}
private async Task ReceiveLoop(ClientWebSocket ws, CancellationTokenSource cts)
{
var buffer = new byte[8192];
var messageBuilder = new StringBuilder();
try
{
while (ws.State == WebSocketState.Open && !cts.IsCancellationRequested)
{
var result = await ws.ReceiveAsync(
new ArraySegment<byte>(buffer),
cts.Token
);
if (result.MessageType == WebSocketMessageType.Text)
{
messageBuilder.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
if (result.EndOfMessage)
{
var message = messageBuilder.ToString();
messageBuilder.Clear();
lock (messageQueue)
{
messageQueue.Enqueue(message);
}
}
}
else if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
}
}
catch (Exception e)
{
if (!cts.IsCancellationRequested)
{
SynLog.Warn($"[HTTP WebSocket] Receive error: {e.Message}");
}
}
finally
{
// Only mutate shared state + auto-reconnect if this loop is STILL the active
// session. A later Connect() overwrites webSocket/cancellationTokenSource, so
// reference-equality on the snapshots tells us whether we're stale.
bool stillActive = ReferenceEquals(webSocket, ws)
&& ReferenceEquals(cancellationTokenSource, cts);
if (stillActive)
{
isConnected = false;
}
if (shouldReconnect
&& stillActive
&& !cts.IsCancellationRequested
&& !connectInFlight)
{
var reconnectTask = Task.Run(async () =>
{
await Task.Delay(reconnectDelay);
if (shouldReconnect && !connectInFlight)
await Connect(port);
});
LogTaskFault(reconnectTask, "AutoReconnect");
}
}
}
public void ProcessMessages()
{
if (!isConnected) return;
lock (messageQueue)
{
while (messageQueue.Count > 0)
{
var message = messageQueue.Dequeue();
try
{
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(message);
if (data != null && data.ContainsKey("type"))
{
ProcessOperation(data);
}
}
catch (Exception e)
{
Debug.LogError($"[HTTP WebSocket] Message processing error: {e.Message}");
}
}
}
}
private void ProcessOperation(Dictionary<string, object> data)
{
var type = data["type"].ToString();
if (type == "operation")
{
var operationType = data.ContainsKey("operationType") ? data["operationType"].ToString() : "";
var operationId = data.ContainsKey("operationId") ? data["operationId"].ToString() : "";
var parameters = data.ContainsKey("parameters") ? data["parameters"] as Newtonsoft.Json.Linq.JObject : null;
// Execute immediately (ProcessMessages is called from main thread via EditorApplication.update)
_ = ExecuteOperation(operationType, operationId, parameters);
}
}
private async Task ExecuteOperation(string operationType, string operationId, Newtonsoft.Json.Linq.JObject parameters)
{
try
{
var operation = new NexusUnityOperation
{
type = operationType,
parameters = new Dictionary<string, string>()
};
// Convert parameters
if (parameters != null)
{
foreach (var prop in parameters.Properties())
{
var value = prop.Value;
if (value is Newtonsoft.Json.Linq.JObject jObj)
{
if (jObj.ContainsKey("x") && jObj.ContainsKey("y") && jObj.ContainsKey("z"))
{
operation.parameters[prop.Name] = $"{jObj["x"]},{jObj["y"]},{jObj["z"]}";
}
else
{
operation.parameters[prop.Name] = value.ToString();
}
}
else
{
operation.parameters[prop.Name] = value?.ToString() ?? "";
}
}
}
// Execute
var executor = new NexusUnityExecutor();
var result = await executor.ExecuteOperation(operation);
var success = !result.StartsWith("Error") && !result.Contains("failed");
// Send response
var response = new Dictionary<string, object>
{
["operationId"] = operationId,
["success"] = success,
["result"] = result
};
await SendMessage(response);
}
catch (Exception e)
{
var errorResponse = new Dictionary<string, object>
{
["operationId"] = operationId,
["success"] = false,
["result"] = $"Error: {e.Message}"
};
await SendMessage(errorResponse);
}
}
public async Task SendMessage(object data)
{
if (!isConnected || webSocket?.State != WebSocketState.Open)
{
return;
}
try
{
var json = JsonConvert.SerializeObject(data);
var bytes = Encoding.UTF8.GetBytes(json);
await webSocket.SendAsync(
new ArraySegment<byte>(bytes),
WebSocketMessageType.Text,
true,
cancellationTokenSource.Token
);
}
catch (Exception e)
{
Debug.LogError($"[HTTP WebSocket] Send error: {e.Message}");
}
}
public async Task Disconnect()
{
shouldReconnect = false;
isConnected = false;
try
{
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closing",
cts.Token
);
}
}
}
catch { }
try
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
catch { }
cancellationTokenSource = null;
webSocket = null;
if (backgroundTasks.Count > 0)
{
try { Task.WhenAll(backgroundTasks).Wait(TimeSpan.FromSeconds(2)); } catch { }
backgroundTasks.Clear();
}
SynLog.Info("[HTTP WebSocket] Disconnected");
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 42c5fbdad694f48c4a4eb6ca71c4e8af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/NexusWebSocketClient.cs
uploadId: 920982
@@ -1,223 +0,0 @@
using UnityEngine;
using SynapticAIPro;
using UnityEditor;
using UnityEngine.Rendering;
using System.IO;
using System.Linq;
namespace Synaptic.Editor
{
/// <summary>
/// Automatically manages shader files based on the current render pipeline.
/// Disables shaders for pipelines that are not installed to prevent compilation errors.
/// </summary>
[InitializeOnLoad]
public static class ShaderPipelineManager
{
public enum RenderPipelineType
{
BuiltIn,
URP,
HDRP
}
private const string SHADER_BASE_PATH = "Assets/Synaptic AI Pro/Shaders";
private const string BUILTIN_FOLDER = "BuiltIn";
private const string URP_FOLDER = "URP";
private const string HDRP_FOLDER = "HDRP";
private const string DISABLED_EXTENSION = ".disabled";
private static readonly string[] PIPELINE_FOLDERS = { BUILTIN_FOLDER, URP_FOLDER, HDRP_FOLDER };
static ShaderPipelineManager()
{
// Delay execution to ensure Unity is fully initialized
EditorApplication.delayCall += OnEditorInitialized;
}
private static void OnEditorInitialized()
{
EditorApplication.delayCall -= OnEditorInitialized;
// Check and update shaders on editor load
UpdateShadersForCurrentPipeline();
// Subscribe to pipeline changes
RenderPipelineManager.activeRenderPipelineTypeChanged += OnPipelineChanged;
}
private static void OnPipelineChanged()
{
SynLog.Info("[Synaptic] Render pipeline changed, updating shaders...");
UpdateShadersForCurrentPipeline();
}
/// <summary>
/// Detects the currently active render pipeline.
/// </summary>
public static RenderPipelineType DetectCurrentPipeline()
{
var pipelineAsset = GraphicsSettings.currentRenderPipeline;
if (pipelineAsset == null)
{
return RenderPipelineType.BuiltIn;
}
var typeName = pipelineAsset.GetType().FullName;
if (typeName.Contains("Universal") || typeName.Contains("URP"))
{
return RenderPipelineType.URP;
}
if (typeName.Contains("HighDefinition") || typeName.Contains("HDRenderPipeline") || typeName.Contains("HDRP"))
{
return RenderPipelineType.HDRP;
}
return RenderPipelineType.BuiltIn;
}
/// <summary>
/// Updates shader files based on the current pipeline.
/// Enables shaders for the current pipeline and disables others.
/// </summary>
[MenuItem("Tools/Synaptic Pro/Update Shaders for Pipeline")]
public static void UpdateShadersForCurrentPipeline()
{
var currentPipeline = DetectCurrentPipeline();
SynLog.Info($"[Synaptic] Detected render pipeline: {currentPipeline}");
bool changed = false;
foreach (var folder in PIPELINE_FOLDERS)
{
string folderPath = Path.Combine(SHADER_BASE_PATH, folder);
if (!Directory.Exists(folderPath))
{
continue;
}
bool shouldEnable = ShouldEnableFolder(folder, currentPipeline);
changed |= UpdateShaderFolder(folderPath, shouldEnable);
}
if (changed)
{
AssetDatabase.Refresh();
SynLog.Info($"[Synaptic] Shaders updated for {currentPipeline} pipeline");
}
}
private static bool ShouldEnableFolder(string folder, RenderPipelineType currentPipeline)
{
switch (folder)
{
case BUILTIN_FOLDER:
return currentPipeline == RenderPipelineType.BuiltIn;
case URP_FOLDER:
return currentPipeline == RenderPipelineType.URP;
case HDRP_FOLDER:
return currentPipeline == RenderPipelineType.HDRP;
default:
return true;
}
}
private static bool UpdateShaderFolder(string folderPath, bool enable)
{
bool changed = false;
// Get all shader files (both enabled and disabled)
var shaderFiles = Directory.GetFiles(folderPath, "*.shader", SearchOption.AllDirectories)
.Concat(Directory.GetFiles(folderPath, "*.shader" + DISABLED_EXTENSION, SearchOption.AllDirectories))
.ToArray();
foreach (var filePath in shaderFiles)
{
bool isDisabled = filePath.EndsWith(DISABLED_EXTENSION);
if (enable && isDisabled)
{
// Enable: remove .disabled extension
string newPath = filePath.Substring(0, filePath.Length - DISABLED_EXTENSION.Length);
MoveFile(filePath, newPath);
changed = true;
}
else if (!enable && !isDisabled)
{
// Disable: add .disabled extension
string newPath = filePath + DISABLED_EXTENSION;
MoveFile(filePath, newPath);
changed = true;
}
}
return changed;
}
private static void MoveFile(string sourcePath, string destPath)
{
// Also handle .meta files
string sourceMetaPath = sourcePath + ".meta";
string destMetaPath = destPath + ".meta";
try
{
if (File.Exists(sourcePath))
{
File.Move(sourcePath, destPath);
}
if (File.Exists(sourceMetaPath))
{
File.Move(sourceMetaPath, destMetaPath);
}
}
catch (System.Exception e)
{
SynLog.Warn($"[Synaptic] Failed to move shader file: {e.Message}");
}
}
/// <summary>
/// Gets the appropriate shader name for the current pipeline.
/// </summary>
public static string GetShaderName(string baseShaderName)
{
var pipeline = DetectCurrentPipeline();
string pipelineFolder = GetPipelineFolderName(pipeline);
return $"Synaptic/{pipelineFolder}/{baseShaderName}";
}
/// <summary>
/// Gets the folder name for a specific pipeline type.
/// </summary>
public static string GetPipelineFolderName(RenderPipelineType pipeline)
{
switch (pipeline)
{
case RenderPipelineType.URP:
return URP_FOLDER;
case RenderPipelineType.HDRP:
return HDRP_FOLDER;
default:
return BUILTIN_FOLDER;
}
}
/// <summary>
/// Finds a shader compatible with the current pipeline.
/// Uses PackageRequirements-enabled multi-SubShader approach.
/// </summary>
public static Shader FindShaderForCurrentPipeline(string baseShaderName)
{
// Shaders now use PackageRequirements tags, so Unity automatically
// skips SubShaders for missing render pipelines.
// Just use the standard shader name.
return Shader.Find($"Synaptic/{baseShaderName}");
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 37c91c2c6cc704a91acff688d62c58e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/ShaderPipelineManager.cs
uploadId: 920982
-72
View File
@@ -1,72 +0,0 @@
using UnityEngine;
using UnityEditor;
namespace SynapticAIPro
{
/// <summary>
/// Synaptic AI Pro 内部ログのラッパー。
/// EditorPrefs "Synaptic.VerboseLog" で Info/Warn の出力を抑制可能。
/// Error は常に出力される(重要なエラーは隠さない)。
/// </summary>
public static class SynLog
{
private const string PREF_KEY = "Synaptic.VerboseLog";
// EditorPrefs.GetBool is main-thread only. Calling it from background
// threads (e.g. WebSocket ReceiveAsync handlers) throws and silently
// kills the calling Task. We snapshot the value into a volatile bool
// on the main thread, and Info/Warn read the cache (thread-safe).
// This was the root cause of v1.2.20/v1.2.21 MCP timeout (ESC-0102).
private static volatile bool _verboseCached = true;
private static bool _initialized = false;
[InitializeOnLoadMethod]
private static void InitVerboseCache()
{
try { _verboseCached = EditorPrefs.GetBool(PREF_KEY, true); }
catch { _verboseCached = true; }
_initialized = true;
}
public static bool VerboseEnabled
{
get => _initialized ? _verboseCached : true;
set
{
_verboseCached = value;
try { EditorPrefs.SetBool(PREF_KEY, value); } catch { }
}
}
public static void Info(string msg)
{
if (_verboseCached) Debug.Log(msg);
}
public static void Info(string msg, Object context)
{
if (_verboseCached) Debug.Log(msg, context);
}
public static void Warn(string msg)
{
if (_verboseCached) Debug.LogWarning(msg);
}
public static void Warn(string msg, Object context)
{
if (_verboseCached) Debug.LogWarning(msg, context);
}
// Error は常に出力(重要な情報)
public static void Error(string msg)
{
Debug.LogError(msg);
}
public static void Error(string msg, Object context)
{
Debug.LogError(msg, context);
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: 531ccf9e9ffa64e5b921f423f2af6fd6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/SynLog.cs
uploadId: 920982
@@ -1,185 +0,0 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEngine;
namespace SynapticPro
{
/// <summary>
/// Launches Node.js HTTP server detached from Unity's Win32 JobObject.
///
/// Unity Editor on Windows assigns a Job Object with
/// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE to itself; any child process started
/// via Process.Start inherits the Job and is killed when Unity manipulates
/// the Job (assembly reload, PlayMode transitions, certain GC paths).
///
/// This launcher uses CreateProcessW via P/Invoke with
/// CREATE_BREAKAWAY_FROM_JOB so the spawned node.exe is independent of
/// Unity's Job. Combined with a parent-PID watchdog in http-server.js the
/// child is reliably tied to Unity's lifecycle without being subject to
/// Job-Object cascade kills.
///
/// PID is persisted in SessionState so the same process can be re-attached
/// after domain reload (recovers ESC-0095 "Connect Unity Only" case).
/// </summary>
public static class SynapticDetachedProcess
{
public const string PID_KEY = "Synaptic.NodeServer.PID";
public const string PORT_KEY = "Synaptic.NodeServer.PORT";
// ----- Win32 P/Invoke -----
private const uint CREATE_BREAKAWAY_FROM_JOB = 0x01000000;
private const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
private const uint DETACHED_PROCESS = 0x00000008;
private const uint CREATE_NO_WINDOW = 0x08000000;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute;
public int dwFlags;
public short wShowWindow, cbReserved2;
public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess, hThread;
public int dwProcessId, dwThreadId;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool CreateProcessW(
string lpApplicationName, string lpCommandLine,
IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,
bool bInheritHandles, uint dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr h);
/// <summary>
/// Start node.exe http-server.js detached from Unity's Job.
/// Returns spawned PID on success, 0 on failure.
/// Windows only — caller falls back to Process.Start on other platforms.
/// </summary>
public static int StartWindows(string nodeExe, string scriptPath, int port, string mcpServerDir, string logFile)
{
int unityPid = Process.GetCurrentProcess().Id;
string cmd = $"\"{nodeExe}\" \"{scriptPath}\" {port} --parent-pid={unityPid} --log=\"{logFile}\"";
var si = new STARTUPINFO { cb = Marshal.SizeOf<STARTUPINFO>() };
uint flags = CREATE_BREAKAWAY_FROM_JOB
| DETACHED_PROCESS
| CREATE_NEW_PROCESS_GROUP
| CREATE_NO_WINDOW
| CREATE_UNICODE_ENVIRONMENT;
bool ok = CreateProcessW(
null, cmd,
IntPtr.Zero, IntPtr.Zero,
false, flags,
IntPtr.Zero, mcpServerDir,
ref si, out PROCESS_INFORMATION pi);
// Fallback: if BREAKAWAY denied (ACCESS_DENIED on JobObject without
// JOB_OBJECT_LIMIT_BREAKAWAY_OK), retry with DETACHED only and rely
// on node-side parent-PID watchdog for orphan cleanup.
if (!ok)
{
int err = Marshal.GetLastWin32Error();
UnityEngine.Debug.LogWarning($"[Synaptic] CreateProcess with BREAKAWAY failed (err={err}). Retrying without BREAKAWAY.");
flags &= ~CREATE_BREAKAWAY_FROM_JOB;
ok = CreateProcessW(
null, cmd,
IntPtr.Zero, IntPtr.Zero,
false, flags,
IntPtr.Zero, mcpServerDir,
ref si, out pi);
if (!ok)
{
int err2 = Marshal.GetLastWin32Error();
UnityEngine.Debug.LogError($"[Synaptic] CreateProcess fallback also failed (err={err2}).");
return 0;
}
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
SessionState.SetInt(PID_KEY, pi.dwProcessId);
SessionState.SetInt(PORT_KEY, port);
EditorPrefs.SetInt(PID_KEY, pi.dwProcessId);
EditorPrefs.SetInt(PORT_KEY, port);
return pi.dwProcessId;
}
/// <summary>
/// Check whether the previously-spawned PID is still alive.
/// Used after domain reload to recover the connection rather than
/// re-spawning.
/// </summary>
public static bool IsStoredProcessAlive(out int pid, out int port)
{
pid = SessionState.GetInt(PID_KEY, 0);
port = SessionState.GetInt(PORT_KEY, 0);
if (pid == 0) { pid = EditorPrefs.GetInt(PID_KEY, 0); port = EditorPrefs.GetInt(PORT_KEY, 0); }
if (pid == 0) return false;
try
{
var p = Process.GetProcessById(pid);
if (p == null || p.HasExited) return false;
// Sanity: must be a node process
string name = p.ProcessName.ToLowerInvariant();
return name.Contains("node");
}
catch
{
return false;
}
}
public static void ClearStoredPid()
{
SessionState.SetInt(PID_KEY, 0);
SessionState.SetInt(PORT_KEY, 0);
EditorPrefs.DeleteKey(PID_KEY);
EditorPrefs.DeleteKey(PORT_KEY);
}
public static bool KillStored()
{
int pid = SessionState.GetInt(PID_KEY, 0);
if (pid == 0) pid = EditorPrefs.GetInt(PID_KEY, 0);
if (pid == 0) return false;
try
{
var p = Process.GetProcessById(pid);
if (!p.HasExited)
{
p.Kill();
p.WaitForExit(3000);
}
return true;
}
catch
{
return false;
}
finally
{
ClearStoredPid();
}
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: d417153078dbb4acd965a32601264b9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/SynapticDetachedProcess.cs
uploadId: 920982
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: fcd8fd59bc5d340b0a82979773cfe49d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,23 +0,0 @@
{
"name": "Synaptic.MCP.Unity.TestRunner",
"rootNamespace": "SynapticPro.TestRunner",
"references": [
"Synaptic.MCP.Unity.Editor",
"Unity.Nuget.Newtonsoft-Json",
"UnityEditor.TestRunner",
"UnityEngine.TestRunner"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 918ef77181e7a491d91f95e03029ffdf
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/TestRunner/NexusAI.TestRunner.asmdef
uploadId: 920982
@@ -1,234 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.TestTools.TestRunner.Api;
using Newtonsoft.Json;
namespace SynapticPro.TestRunner
{
/// <summary>
/// TestRunner service that can be called from NexusExecutor via reflection.
/// This class is only compiled when UNITY_INCLUDE_TESTS is defined.
/// </summary>
public static class NexusTestRunnerService
{
// Static fields to store test execution state
private static bool _isTestRunning = false;
private static List<TestResultInfo> _testResults = new List<TestResultInfo>();
private static int _totalTests = 0;
private static int _completedTests = 0;
private static string _currentTestMode = "";
private static TestRunnerApi _testRunnerApi;
private class TestResultInfo
{
public string name;
public string status;
public double duration;
public string message;
}
/// <summary>
/// Main entry point - called from NexusExecutor via reflection
/// </summary>
public static string Execute(string operation, string mode, string filter)
{
try
{
switch (operation.ToLower())
{
case "run":
return RunTestsWithApi(mode, filter);
case "results":
return GetTestResults();
case "list":
return ListAvailableTests(mode);
default:
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown operation: {operation}. Use 'run', 'results', or 'list'."
});
}
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private static string RunTestsWithApi(string testMode, string filter)
{
if (_isTestRunning)
{
return JsonConvert.SerializeObject(new
{
success = true,
status = "running",
mode = _currentTestMode,
totalTests = _totalTests,
completedTests = _completedTests,
message = "Tests are still running. Use operation='results' to check progress."
});
}
// Reset state
_isTestRunning = true;
_testResults.Clear();
_totalTests = 0;
_completedTests = 0;
_currentTestMode = testMode;
// Create TestRunnerApi instance
_testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
// Register callbacks
_testRunnerApi.RegisterCallbacks(new SynapticTestCallbacks());
// Build filter
var testModeEnum = testMode.ToLower() == "playmode"
? TestMode.PlayMode
: TestMode.EditMode;
var executionSettings = new ExecutionSettings
{
filters = new[] { new Filter { testMode = testModeEnum } }
};
if (!string.IsNullOrEmpty(filter))
{
executionSettings.filters[0].testNames = new[] { filter };
}
// Start execution
_testRunnerApi.Execute(executionSettings);
return JsonConvert.SerializeObject(new
{
success = true,
status = "started",
mode = testMode,
message = "Tests started. Use operation='results' to check progress and get results."
});
}
private static string GetTestResults()
{
return JsonConvert.SerializeObject(new
{
success = true,
isRunning = _isTestRunning,
mode = _currentTestMode,
totalTests = _totalTests,
completedTests = _completedTests,
results = _testResults.Select(r => new
{
name = r.name,
status = r.status,
duration = r.duration,
message = r.message
}).ToList(),
summary = new
{
passed = _testResults.Count(r => r.status == "Passed"),
failed = _testResults.Count(r => r.status == "Failed"),
skipped = _testResults.Count(r => r.status == "Skipped")
}
}, Formatting.Indented);
}
private static string ListAvailableTests(string testMode)
{
var api = ScriptableObject.CreateInstance<TestRunnerApi>();
var testModeEnum = testMode.ToLower() == "playmode"
? TestMode.PlayMode
: TestMode.EditMode;
var tests = new List<string>();
api.RetrieveTestList(testModeEnum, (testRoot) =>
{
CollectTestNames(testRoot, tests);
});
return JsonConvert.SerializeObject(new
{
success = true,
mode = testMode,
testCount = tests.Count,
tests = tests
}, Formatting.Indented);
}
private static void CollectTestNames(ITestAdaptor test, List<string> tests)
{
if (test == null) return;
if (test.IsSuite)
{
foreach (var child in test.Children)
{
CollectTestNames(child, tests);
}
}
else
{
tests.Add(test.FullName);
}
}
private class SynapticTestCallbacks : ICallbacks
{
public void RunStarted(ITestAdaptor testsToRun)
{
_totalTests = CountTests(testsToRun);
Debug.Log($"[Synaptic] Test run started. Total tests: {_totalTests}");
}
public void RunFinished(ITestResultAdaptor result)
{
_isTestRunning = false;
Debug.Log($"[Synaptic] Test run finished. Passed: {_testResults.Count(r => r.status == "Passed")}, Failed: {_testResults.Count(r => r.status == "Failed")}");
}
public void TestStarted(ITestAdaptor test)
{
// Optional: Log test start
}
public void TestFinished(ITestResultAdaptor result)
{
_completedTests++;
var testResult = new TestResultInfo
{
name = result.Test.FullName,
status = result.TestStatus.ToString(),
duration = result.Duration,
message = result.Message ?? ""
};
_testResults.Add(testResult);
}
private int CountTests(ITestAdaptor test)
{
if (test == null) return 0;
if (test.IsSuite)
{
int count = 0;
foreach (var child in test.Children)
{
count += CountTests(child);
}
return count;
}
return 1;
}
}
}
}
@@ -1,18 +0,0 @@
fileFormatVersion: 2
guid: f9fcb2cb1828f43ac8ccb7e67814d504
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/Editor/TestRunner/NexusTestRunnerService.cs
uploadId: 920982
-83
View File
@@ -1,83 +0,0 @@
# Synaptic AI Pro for Unity — License
Copyright (c) 2024-2026 mizumaru (Synaptic)
All rights reserved.
## End User License Agreement (EULA)
This software ("Synaptic AI Pro for Unity") is licensed, not sold.
By purchasing or installing this software, you agree to the following terms.
### 1. License Grant
The author grants you a **non-exclusive, non-transferable, perpetual license**
to use this software within Unity projects, subject to the terms below.
### 2. Permitted Use
You **may**:
- Use the software in personal projects.
- Use the software in commercial projects (including game development,
VRChat content, asset creation, and client work).
- Use the software in unlimited Unity projects.
- Install the software on multiple machines that you personally own.
- Modify the software for your own internal use.
### 3. Restrictions
You **may not**:
- Redistribute, resell, or sublicense the software in source or binary form.
- Bundle the software as part of another asset, tool, or template for resale.
- Upload the software (or modified versions) to public repositories, asset
stores, marketplaces, or file-sharing services.
- Share your license key with other users.
- Remove or alter copyright notices, license texts, or attribution.
- Use the software to develop competing AI-driven Unity automation products
intended for resale.
### 4. License Key
Each purchase grants one license key per user. License keys are tied to
the purchaser and may not be transferred without written consent from the
author.
### 5. Updates
Updates released during your license validity period are free of charge
and provided at the author's discretion.
### 6. Third-Party Components
This product includes third-party components. See `THIRD_PARTY_NOTICES.md`
for the full list of components and their respective licenses.
### 7. Warranty Disclaimer
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
### 8. Limitation of Liability
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
### 9. Refund Policy
As this is a digital product, refunds are generally not provided.
However, if you experience technical issues that cannot be resolved
within a reasonable time frame, please contact the author at the
support email below.
### 10. Contact
- Email: sekiguchimiu@gmail.com
- Discord: https://discord.gg/Y2nUyWvqR3
- Website: https://synaptic-ai.net
- BOOTH: https://miu-chang.booth.pm
---
Last updated: 2026-05-12
-14
View File
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: f21d8d26d1a5f4b208e1128dafacf1c2
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/LICENSE.md
uploadId: 920982
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ea4e4f254bc664e9cb731940557cd798
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,17 +0,0 @@
# Unity MCP Server
## Documentation
- MCP Documentation: https://modelcontextprotocol.io/docs
- MCP Specification: https://modelcontextprotocol.io/specification/2025-06-18
- MCP SDK: https://www.npmjs.com/package/@modelcontextprotocol/sdk
## API Keys
- Claude API: https://console.anthropic.com/
- Gemini API: https://aistudio.google.com/app/apikey
- OpenAI API: https://platform.openai.com/api-keys
## Setup
1. Run `npm install` to install dependencies
2. Configure API keys in .env file
3. Run `npm start` to start the server
# nexus
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 4541bcf45eb234d2084d1684d4e83b4d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/README.md
uploadId: 920982
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 5c621d6320333478b8306b899b61ab86
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,7 +0,0 @@
{
"provider": "anthropic",
"model": "claude-3-opus-20240229",
"temperature": 0.7,
"max_tokens": 4096,
"tools": ["unity_create", "unity_modify", "unity_delete"]
}
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 7b83a451a66764293a632db12c102d92
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/claude.json
uploadId: 920982
@@ -1,6 +0,0 @@
{
"provider": "github",
"model": "gpt-4",
"temperature": 0.5,
"tools": ["unity_create", "unity_modify", "code_generation"]
}
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 02c26a063763b46768be7b91db398d95
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/copilot.json
uploadId: 920982
@@ -1,6 +0,0 @@
{
"provider": "google",
"model": "gemini-pro",
"temperature": 0.8,
"tools": ["unity_create", "unity_modify"]
}
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: e7817e5dc9aeb4b21be729f00bc21755
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/ai-config/gemini.json
uploadId: 920982
@@ -1,131 +0,0 @@
// Unity内チャットとデスクトップアプリのブリッジ処理
class BridgeHandler {
constructor() {
this.unitySocket = null;
this.desktopSocket = null;
this.conversationHistory = [];
this.pendingToolExecutions = new Map();
}
// Unity接続を設定
setUnityConnection(socket) {
this.unitySocket = socket;
// console.log('[Bridge] Unity connected');
// 既存の会話履歴を送信
if (this.conversationHistory.length > 0) {
socket.send(JSON.stringify({
type: 'conversation_history',
history: this.conversationHistory
}));
}
}
// デスクトップアプリ接続を設定
setDesktopConnection(socket) {
this.desktopSocket = socket;
// console.log('[Bridge] Desktop app connected');
}
// Unity → デスクトップアプリへのメッセージ転送
forwardToDesktop(message) {
if (!this.desktopSocket) {
// デスクトップアプリ未接続の場合はエラー
if (this.unitySocket) {
this.unitySocket.send(JSON.stringify({
type: 'error',
message: 'デスクトップアプリが接続されていません。Claude DesktopまたはGPT Desktopを起動してください。'
}));
}
return;
}
// 会話履歴に追加
this.conversationHistory.push({
role: 'user',
content: message.content,
timestamp: new Date()
});
// デスクトップアプリに転送
this.desktopSocket.send(JSON.stringify({
type: 'user_message',
content: message.content,
context: {
source: 'unity',
projectName: message.projectName || 'Unity Project',
history: this.conversationHistory.slice(-10) // 直近10件の履歴を含める
}
}));
}
// デスクトップアプリ → Unityへのメッセージ転送
forwardToUnity(message) {
if (!this.unitySocket) {
console.error('[Bridge] Unity not connected');
return;
}
// AIの応答を会話履歴に追加
if (message.type === 'assistant_response') {
this.conversationHistory.push({
role: 'assistant',
content: message.content,
timestamp: new Date()
});
}
// Unityに転送
this.unitySocket.send(JSON.stringify(message));
}
// ツール実行結果の処理
handleToolResult(toolId, result) {
if (this.pendingToolExecutions.has(toolId)) {
const { desktopRequestId } = this.pendingToolExecutions.get(toolId);
// デスクトップアプリに結果を送信
if (this.desktopSocket) {
this.desktopSocket.send(JSON.stringify({
type: 'tool_result',
requestId: desktopRequestId,
result: result
}));
}
this.pendingToolExecutions.delete(toolId);
}
}
// 接続切断の処理
handleDisconnect(type) {
if (type === 'unity') {
this.unitySocket = null;
// console.log('[Bridge] Unity disconnected');
} else if (type === 'desktop') {
this.desktopSocket = null;
// console.log('[Bridge] Desktop app disconnected');
// Unityに通知
if (this.unitySocket) {
this.unitySocket.send(JSON.stringify({
type: 'desktop_disconnected',
message: 'デスクトップアプリとの接続が切断されました'
}));
}
}
}
// 状態を取得
getStatus() {
return {
unityConnected: this.unitySocket !== null,
desktopConnected: this.desktopSocket !== null,
historyLength: this.conversationHistory.length,
pendingTools: this.pendingToolExecutions.size
};
}
}
module.exports = BridgeHandler;
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 41ee4dc4328d94f88b3d472abf8dce2e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/bridge-handler.js
uploadId: 920982
@@ -1,79 +0,0 @@
unity_create_gameobject
unity_update_gameobject
unity_delete_gameobject
unity_duplicate_gameobject
unity_find_gameobjects_by_component
unity_group_gameobjects
unity_get_gameobjects_list
unity_get_gameobject_detail
unity_set_active_scene
unity_find_missing_references
unity_set_transform
unity_add_component
unity_update_component
unity_setup_camera
unity_create_virtual_camera
unity_create_freelook_camera
unity_setup_cinemachine_brain
unity_update_virtual_camera
unity_create_dolly_track
unity_add_collider_extension
unity_set_camera_priority
unity_set_camera_enabled
unity_update_camera_target
unity_update_brain_blend_settings
unity_get_active_camera_info
unity_capture_game_view
unity_capture_scene_view
unity_capture_region
unity_get_screenshot_result
unity_get_scene_info
unity_get_scene_summary
unity_load_scene
unity_unload_scene
unity_list_all_scenes
unity_add_scene_to_build
unity_get_scene_changes_since
unity_pause_scene
unity_search
unity_force_refresh_assets
unity_invoke_context_menu
unity_get_inspector_info
unity_get_selected_object_info
unity_get_component_details
unity_console
unity_analyze_console_logs
unity_create_ui
unity_setup_ui_canvas
unity_set_ui_anchor
unity_setup_ui_anchors
unity_create_ui_grid
unity_create_ui_dialog
unity_setup_safe_area
unity_create_material
unity_setup_material
unity_setup_lighting
unity_setup_reflection_probe
unity_create_light_probe_group
unity_setup_lighting_preset
unity_create_animator_controller
unity_add_animation_state
unity_create_animation_clip
unity_add_animation_transition
unity_bake_animation
unity_setup_physics
unity_setup_navmesh
unity_create_particle_system
unity_create_screen_fade
unity_create_screen_shake
unity_create_particle_preset
unity_create_prefab
unity_search_prefabs_by_component
unity_find_material_usage
unity_get_asset_dependencies
unity_check_folder
unity_create_folder
unity_list_assets
unity_create_audio_source
unity_setup_3d_audio
unity_list_packages
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 3153b89fb08084e9f95098bc38ab2fec
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/essential-tools-list.txt
uploadId: 920982
@@ -1,56 +0,0 @@
#!/usr/bin/env python3
import re
# Read essential tools list
with open('essential-tools-list.txt', 'r') as f:
essential_tools = set(line.strip() for line in f if line.strip())
print(f"Essential tools count: {len(essential_tools)}")
# Read index.js
with open('index.js', 'r') as f:
content = f.read()
# Split by tool registrations
# Pattern: mcpServer.registerTool('tool_name', ...);
pattern = r"( mcpServer\.registerTool\('([^']+)',[\s\S]*?\n \}\);)"
matches = list(re.finditer(pattern, content))
print(f"Found {len(matches)} tool registrations")
# Find the start of tool registrations
first_match_start = matches[0].start() if matches else -1
last_match_end = matches[-1].end() if matches else -1
# Extract prefix (before first tool), suffix (after last tool), and tools
prefix = content[:first_match_start]
suffix = content[last_match_end:]
# Filter tools
filtered_tools = []
removed_count = 0
kept_count = 0
for match in matches:
tool_block = match.group(1)
tool_name = match.group(2)
if tool_name in essential_tools:
filtered_tools.append(tool_block)
kept_count += 1
print(f"✓ Keeping: {tool_name}")
else:
removed_count += 1
print(f"✗ Removing: {tool_name}")
# Reconstruct file
output = prefix + '\n\n'.join(filtered_tools) + suffix
# Write to index-essential.js
with open('index-essential.js', 'w') as f:
f.write(output)
print(f"\n=== Summary ===")
print(f"Kept: {kept_count} tools")
print(f"Removed: {removed_count} tools")
print(f"Output written to: index-essential.js")
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: abb00cd6e72444e239439930c4157fe0
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/filter-essential-tools.py
uploadId: 920982
@@ -1,613 +0,0 @@
#!/usr/bin/env node
/**
* Synaptic AI Pro - HTTP Server (Standalone)
*
* Node.js based HTTP server for Unity control.
* Runs outside Unity process - no domain reload issues.
*
* Usage:
* node http-server.js [port]
* node http-server.js 8086
*
* Environment variables:
* HTTP_PORT - Server port (default: 8086)
*
* HTTP and WebSocket run on the SAME port.
* - HTTP: http://localhost:8086/
* - WebSocket: ws://localhost:8086/
*/
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
// ===== Configuration =====
const PORT = parseInt(process.argv[2]) || parseInt(process.env.HTTP_PORT) || 8086;
// ===== Parent-PID watchdog =====
// When launched detached from Unity's JobObject (Windows), we lose the
// guarantee that the OS will reap us if Unity dies. Poll the parent PID
// every 5s and self-terminate if it's gone — prevents orphaned node.exe.
(function setupParentWatchdog() {
const arg = (process.argv.find(a => typeof a === 'string' && a.startsWith('--parent-pid=')) || '');
const parentPid = arg ? parseInt(arg.split('=')[1], 10) : 0;
if (!parentPid) return;
setInterval(() => {
try {
// Signal 0 is a "check process exists" probe on both POSIX and Windows
process.kill(parentPid, 0);
} catch (_e) {
console.error(`[watchdog] Parent PID ${parentPid} gone. Exiting.`);
process.exit(0);
}
}, 5000).unref();
})();
// ===== File log (when --log=path is provided) =====
// Detached mode disables stdout/stderr piping from C# side, so route logs
// to a file instead. Best-effort: silent failure if file can't be opened.
(function setupFileLog() {
const arg = (process.argv.find(a => typeof a === 'string' && a.startsWith('--log=')) || '');
const logPath = arg ? arg.split('=').slice(1).join('=') : '';
if (!logPath) return;
try {
const fs = require('fs');
const stream = fs.createWriteStream(logPath, { flags: 'a' });
const wrap = (orig) => (...args) => {
try {
const line = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
stream.write(`[${new Date().toISOString()}] ${line}\n`);
} catch (_) { /* ignore */ }
try { orig.apply(console, args); } catch (_) { /* detached: stdout may be closed */ }
};
console.log = wrap(console.log);
console.error = wrap(console.error);
console.warn = wrap(console.warn);
} catch (_e) {
/* ignore: log is best-effort */
}
})();
const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
const server = http.createServer(app);
// ===== Tool Registry =====
let toolRegistry = {};
let toolCategories = {};
function loadToolRegistry() {
try {
const registryPath = path.join(__dirname, 'tool-registry.json');
if (fs.existsSync(registryPath)) {
toolRegistry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
// Build category index
toolCategories = {};
for (const [name, tool] of Object.entries(toolRegistry)) {
const cat = tool.category || 'Other';
if (!toolCategories[cat]) toolCategories[cat] = [];
toolCategories[cat].push({ name, ...tool });
}
console.error(`[HTTP] Loaded ${Object.keys(toolRegistry).length} tools in ${Object.keys(toolCategories).length} categories`);
} else {
console.error('[HTTP] Warning: tool-registry.json not found');
}
} catch (e) {
console.error(`[HTTP] Failed to load tool registry: ${e.message}`);
}
}
loadToolRegistry();
// ===== Unity WebSocket Connection =====
let unitySocket = null;
let wss = null;
const pendingRequests = new Map();
let requestCounter = 0;
function setupWebSocket() {
// Attach WebSocket to the same HTTP server (same port)
wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
if (isUnity || !req.url || req.url === '/') {
if (unitySocket) {
try { unitySocket.close(); } catch (e) {}
}
unitySocket = ws;
console.error(`[HTTP] Unity connected via WebSocket`);
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
// Handle response from Unity
if (data.operationId && pendingRequests.has(data.operationId)) {
const { resolve } = pendingRequests.get(data.operationId);
pendingRequests.delete(data.operationId);
resolve(data);
}
} catch (e) {
console.error(`[HTTP] WebSocket message error: ${e.message}`);
}
});
ws.on('close', () => {
console.error('[HTTP] Unity disconnected');
unitySocket = null;
});
ws.on('error', (err) => {
console.error(`[HTTP] WebSocket error: ${err.message}`);
});
// ESC-0108 fix (reported by xvpower., 2026-05-22):
// Mono's `ClientWebSocket` (Unity) does NOT auto-respond to
// WebSocket protocol-level `ping` frames with `pong` — unlike
// .NET 5+ runtime. The previous `ws.ping() / ws.on('pong')`
// heartbeat therefore terminated the connection after ~30s on
// every Unity 6.3 LTS environment.
// Switch to last-message-seen tracking: Unity already emits its
// own application-level traffic (operation_response, debug logs)
// so we just observe the timestamp of any received frame.
ws.lastSeen = Date.now();
const recordSeen = () => { ws.lastSeen = Date.now(); };
ws.on('message', recordSeen);
ws.on('ping', recordSeen);
ws.on('pong', recordSeen);
}
});
// Heartbeat: declare a connection dead only if no inbound frame for N ms.
// Override via env (UNITY_STALE_TIMEOUT_MS); default 60 000 keeps a safe
// margin over Unity's slowest legitimate quiet period.
const STALE_TIMEOUT_MS = parseInt(process.env.UNITY_STALE_TIMEOUT_MS || '60000', 10);
const heartbeatInterval = setInterval(() => {
if (!wss.clients) return;
wss.clients.forEach((ws) => {
if (!ws.lastSeen) return;
if (Date.now() - ws.lastSeen > STALE_TIMEOUT_MS) {
console.error(`[HTTP] Unity stale (no frames for ${STALE_TIMEOUT_MS}ms) - closing connection`);
try { ws.terminate(); } catch {}
}
});
}, 30000);
wss.on('close', () => {
clearInterval(heartbeatInterval);
});
wss.on('error', (err) => {
console.error(`[HTTP] WebSocket server error: ${err.message}`);
});
console.error(`[HTTP] WebSocket server attached to HTTP server (port ${PORT})`);
}
// Execute tool via Unity WebSocket
function executeOnUnity(toolName, params, timeout = 30000) {
return new Promise((resolve, reject) => {
if (!unitySocket || unitySocket.readyState !== WebSocket.OPEN) {
reject(new Error('Unity not connected. Open Unity with Synaptic AI Pro project.'));
return;
}
const operationId = `http_${++requestCounter}_${Date.now()}`;
const operation = convertToolToOperation(toolName);
const message = {
type: 'operation',
operationType: operation,
operationId: operationId,
parameters: params || {}
};
const timer = setTimeout(() => {
pendingRequests.delete(operationId);
reject(new Error(`Timeout waiting for Unity response (${timeout}ms)`));
}, timeout);
pendingRequests.set(operationId, {
resolve: (data) => {
clearTimeout(timer);
resolve(data);
},
reject: (err) => {
clearTimeout(timer);
reject(err);
}
});
unitySocket.send(JSON.stringify(message));
});
}
// Convert tool name to Unity operation type
function convertToolToOperation(toolName) {
// Remove unity_ prefix and convert to UPPER_SNAKE_CASE
let name = toolName;
if (name.startsWith('unity_')) {
name = name.substring(6);
}
return name.toUpperCase();
}
// ===== Prompt Generation =====
function getAIControlPrompt() {
return `# Synaptic AI Pro (Unity) HTTP Control Instructions
## Prerequisites
- Unity must be open with Synaptic AI Pro project loaded
- HTTP Server running: node http-server.js ${PORT}
- Unity connects via WebSocket automatically
## Endpoints
- GET / or /prompt - Get this AI control prompt + full tools reference
- GET /health - Server status and Unity connection
- GET /categories - List all tool categories
- GET /tools - Full tool registry
- GET /tools/category/:cat - List tools in category with inputSchema
- GET /tools/search?q=keyword - Search tools by name, description, parameters
- GET /tools/reference - Get ALL tools in Markdown format (TOKEN SAVER)
- GET /resources - List available resources (MCP-style)
- GET /resources/read?uri=synaptic://tools/reference - Read a resource
- POST /execute - Execute single tool (RECOMMENDED)
- POST /batch - Execute multiple tools at once (RECOMMENDED)
## Verify connection
curl http://localhost:${PORT}/health
## Tool discovery
curl http://localhost:${PORT}/categories
curl http://localhost:${PORT}/tools/category/scene
## Tool search
curl "http://localhost:${PORT}/tools/search?q=material"
curl "http://localhost:${PORT}/tools/search?q=color&category=Material&limit=10"
## Get full tools reference (RECOMMENDED at session start)
curl "http://localhost:${PORT}/tools/reference"
curl "http://localhost:${PORT}/tools/reference?format=compact"
## Single tool execution (RECOMMENDED)
curl -X POST http://localhost:${PORT}/execute -H "Content-Type: application/json" -d '{"tool":"unity_create_gameobject","params":{"name":"MyCube","type":"cube"}}'
## Batch execution (RECOMMENDED for multiple operations)
curl -X POST http://localhost:${PORT}/batch -H "Content-Type: application/json" -d '[
{"tool":"unity_create_gameobject","params":{"name":"Cube1","type":"cube"}},
{"tool":"unity_set_transform","params":{"name":"Cube1","position":"2,0,0"}},
{"tool":"unity_create_gameobject","params":{"name":"Sphere1","type":"sphere"}}
]'
## If connection fails
- "Unity not connected" → Open Unity project with Synaptic AI Pro
- Connection refused → HTTP server not running (node http-server.js)
## Notes
- All responses are JSON
- Use /batch for multiple operations (more efficient)
- 30 second timeout per request
`;
}
// ===== Tools Reference Generation =====
function getToolsReference(format = 'markdown', category = null) {
const tools = category
? (toolCategories[category] || [])
: Object.entries(toolRegistry).map(([name, t]) => ({ name, ...t }));
if (format === 'compact') {
// Compact format: name | description (one line per tool)
let output = '# Synaptic AI Pro - Tools Reference (Compact)\n\n';
output += `Total: ${tools.length} tools\n\n`;
const byCategory = {};
for (const tool of tools) {
const cat = tool.category || 'Other';
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(tool);
}
for (const [cat, catTools] of Object.entries(byCategory).sort()) {
output += `## ${cat} (${catTools.length})\n`;
for (const tool of catTools) {
const desc = (tool.description || '').split('.')[0].substring(0, 80);
output += `- ${tool.name}: ${desc}\n`;
}
output += '\n';
}
return output;
}
// Full markdown format with inputSchema
let output = '# Synaptic AI Pro - Tools Reference\n\n';
output += `Total: ${tools.length} tools\n\n`;
const byCategory = {};
for (const tool of tools) {
const cat = tool.category || 'Other';
if (!byCategory[cat]) byCategory[cat] = [];
byCategory[cat].push(tool);
}
for (const [cat, catTools] of Object.entries(byCategory).sort()) {
output += `## ${cat}\n\n`;
for (const tool of catTools) {
output += `### ${tool.name}\n`;
output += `${tool.description || 'No description'}\n\n`;
if (tool.inputSchema && tool.inputSchema.properties) {
output += '**Parameters:**\n';
for (const [param, schema] of Object.entries(tool.inputSchema.properties)) {
const required = (tool.inputSchema.required || []).includes(param) ? ' (required)' : '';
const desc = schema.description || '';
output += `- \`${param}\`${required}: ${desc}\n`;
}
output += '\n';
}
}
}
return output;
}
// ===== HTTP Routes =====
// Root - return prompt + full tools reference
app.get('/', (req, res) => {
const prompt = getAIControlPrompt();
const toolsRef = getToolsReference('markdown');
res.type('text/plain').send(prompt + '\n\n---\n\n' + toolsRef);
});
// Prompt endpoint
app.get('/prompt', (req, res) => {
const prompt = getAIControlPrompt();
const toolsRef = getToolsReference('markdown');
res.type('text/plain').send(prompt + '\n\n---\n\n' + toolsRef);
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
server: 'Synaptic AI Pro Unity HTTP Server',
port: PORT,
tools: Object.keys(toolRegistry).length,
unityConnected: unitySocket !== null && unitySocket.readyState === WebSocket.OPEN,
toolCount: Object.keys(toolRegistry).length,
categoryCount: Object.keys(toolCategories).length
});
});
// Categories list
app.get('/categories', (req, res) => {
const categories = Object.entries(toolCategories)
.map(([name, tools]) => ({ name, count: tools.length }))
.sort((a, b) => a.name.localeCompare(b.name));
res.json({ categories });
});
// Full tool registry
app.get('/tools', (req, res) => {
res.json(toolRegistry);
});
// Tool list (Synaptic Code compatible)
app.get('/tools/list', (req, res) => {
const toolNames = Object.keys(toolRegistry);
res.json({
tools: toolNames,
count: toolNames.length
});
});
// Tools by category
app.get('/tools/category/:category', (req, res) => {
const category = req.params.category;
const tools = toolCategories[category];
if (!tools) {
return res.status(404).json({
error: `Category not found: ${category}`,
available: Object.keys(toolCategories).sort()
});
}
res.json({ category, count: tools.length, tools });
});
// Tool search
app.get('/tools/search', (req, res) => {
const query = (req.query.q || '').toLowerCase();
const categoryFilter = req.query.category;
const limit = parseInt(req.query.limit) || 20;
if (!query) {
return res.status(400).json({ error: 'Missing query parameter: q' });
}
const results = [];
const keywords = query.split(/\s+/);
for (const [name, tool] of Object.entries(toolRegistry)) {
if (categoryFilter && tool.category !== categoryFilter) continue;
let score = 0;
const searchText = `${name} ${tool.title || ''} ${tool.description || ''}`.toLowerCase();
for (const kw of keywords) {
if (name.toLowerCase().includes(kw)) score += 10;
if ((tool.title || '').toLowerCase().includes(kw)) score += 5;
if ((tool.description || '').toLowerCase().includes(kw)) score += 2;
}
if (score > 0) {
results.push({ name, score, ...tool });
}
}
results.sort((a, b) => b.score - a.score);
const limited = results.slice(0, limit);
res.json({ query, count: limited.length, total: results.length, results: limited });
});
// Tools reference (markdown)
app.get('/tools/reference', (req, res) => {
const format = req.query.format || 'markdown';
const category = req.query.category || null;
const reference = getToolsReference(format, category);
res.type('text/plain').send(reference);
});
// Resources list (MCP-style)
app.get('/resources', (req, res) => {
res.json({
resources: [
{
uri: 'synaptic://tools/reference',
name: 'Tools Reference (Compact)',
description: 'Complete list of all Unity tools in compact format (~30KB)',
mimeType: 'text/markdown'
},
{
uri: 'synaptic://tools/reference/full',
name: 'Tools Reference (Full)',
description: 'Complete list of all Unity tools with full parameter details (~100KB)',
mimeType: 'text/markdown'
}
]
});
});
// Read resource
app.get('/resources/read', (req, res) => {
const uri = req.query.uri;
if (!uri) {
return res.status(400).json({ error: 'Missing uri parameter' });
}
let content = null;
let mimeType = 'text/markdown';
if (uri === 'synaptic://tools/reference') {
content = getToolsReference('compact');
} else if (uri === 'synaptic://tools/reference/full') {
content = getToolsReference('markdown');
} else {
return res.status(404).json({ error: `Unknown resource: ${uri}` });
}
res.json({
contents: [
{ uri, mimeType, text: content }
]
});
});
// Execute single tool
app.post('/execute', async (req, res) => {
try {
const { tool, params, timeout } = req.body;
if (!tool) {
return res.status(400).json({ error: 'Missing tool name' });
}
const result = await executeOnUnity(tool, params || {}, timeout || 30000);
res.json(result);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// Batch execute
app.post('/batch', async (req, res) => {
try {
const operations = req.body;
if (!Array.isArray(operations)) {
return res.status(400).json({ error: 'Body must be an array of operations' });
}
const results = [];
for (const op of operations) {
try {
const result = await executeOnUnity(op.tool, op.params || {});
results.push({ tool: op.tool, success: true, result });
} catch (e) {
results.push({ tool: op.tool, success: false, error: e.message });
}
}
res.json({ count: results.length, results });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// Legacy: POST /tool/:name
app.post('/tool/:name', async (req, res) => {
try {
const tool = req.params.name;
const params = req.body || {};
const result = await executeOnUnity(tool, params);
res.json(result);
} catch (e) {
res.status(500).json({ error: e.message });
}
});
// ===== Startup =====
server.listen(PORT, () => {
// Setup WebSocket after server starts (attached to same port)
setupWebSocket();
console.error(`
╔═══════════════════════════════════════════════════════════╗
║ Synaptic AI Pro - HTTP Server ║
╠═══════════════════════════════════════════════════════════╣
║ Port: ${PORT.toString().padEnd(5)}
║ HTTP: http://localhost:${PORT.toString().padEnd(5)}
║ WebSocket: ws://localhost:${PORT.toString().padEnd(5)}
╠═══════════════════════════════════════════════════════════╣
║ Endpoints: ║
║ GET / - AI prompt + tools reference ║
║ GET /health - Connection status ║
║ GET /tools - Full tool registry ║
║ GET /categories - Tool categories ║
║ POST /execute - Execute tool ║
║ POST /batch - Batch execute ║
╠═══════════════════════════════════════════════════════════╣
║ Waiting for Unity to connect... ║
╚═══════════════════════════════════════════════════════════╝
`);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.error('\n[HTTP] Shutting down...');
if (wss) wss.close();
server.close();
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('\n[HTTP] Shutting down...');
if (wss) wss.close();
server.close();
process.exit(0);
});
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 1d8af1b2b57c946c29ca26b83004613a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/http-server.js
uploadId: 920982
@@ -1,611 +0,0 @@
#!/usr/bin/env node
/**
* hub-server.js - MCP Hub Server for Unity Synaptic v1.1.2 (Dynamic Mode)
*
* Provides dynamic tool loading for GitHub Copilot (VS Code):
* - Exposes only ~10 management tools initially
* - Loads additional tools on-demand via select_tools()
* - Uses MCP Notification System to update tool list
* - Requires client support for notifications/tools/list_changed
*
* Supported Clients:
* - ✅ GitHub Copilot (VS Code)
* - ❌ Claude Desktop (use index.js instead)
* - ❌ Cursor (use index.js instead)
*
* Usage:
* node hub-server.js (stdio mode for MCP clients)
* OPENAI_API_KEY=sk-xxx node hub-server.js (optional, for keyword search)
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import WebSocket, { WebSocketServer } from 'ws';
import { z } from 'zod';
import express from 'express';
import http from 'http';
import cors from 'cors';
import {
filterToolsByCategories,
searchToolsByKeywords,
getAvailableCategories,
loadToolRegistry
} from './utils/tool-loader.js';
// HTTP and WebSocket server setup
const app = express();
app.use(cors());
app.use(express.json());
const httpServer = http.createServer(app);
let wss = null; // WebSocket server for Unity connections
// Unity WebSocket connection
let unityWs = null;
// Unity command tracking (same as index.js)
const pendingRequests = new Map();
let requestId = 0;
// Load tool registry (embeddings + metadata)
const toolRegistry = loadToolRegistry();
if (!toolRegistry) {
console.error('[Hub] WARNING: tool-registry.json not found. Run: node scripts/generate-tool-registry.js');
console.error('[Hub] Hub server will work with limited functionality');
}
// Initialize MCP Server with dynamic tool capabilities
const server = new Server(
{
name: 'unity-synaptic-hub',
version: '1.1.0',
},
{
capabilities: {
tools: {
listChanged: true // Support for dynamic tool updates via notifications
},
},
}
);
// Essential tools (always available)
//
// RECOMMENDED WORKFLOW FOR LLMs:
// 1. Use list_available_categories() to see all 31 categories
// 2. Use search_tools(query: "keyword") to find specific tools
// 3. Use select_tools(categories: ["Cat1", "Cat2"]) to load needed tools
// 4. Call the loaded Unity tools (e.g., unity_create_gameobject)
//
// Example: User wants to add visual effects
// → search_tools(query: "bloom effect")
// → select_tools(categories: ["VFX"])
// → unity_create_bloom(...)
//
const essentialTools = [
{
name: 'select_tools',
title: '🔧 Load Unity Tools by Category',
description: 'Load additional Unity tools dynamically. Call list_available_categories first to see available categories, or use search_tools to find specific tools. Without calling this, only 8 basic tools are available. Example: select_tools({categories: ["VFX", "Camera"]}) loads visual effects and camera tools.',
inputSchema: z.object({
categories: z.array(z.enum([
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Cinemachine', 'Scene', 'GOAP', 'Audio', 'Input',
'VFX', 'Shader', 'Weather', 'TimeOfDay', 'Editor', 'Package', 'Build',
'Monitoring', 'AssetManagement', 'Optimization', 'Batch', 'GameSystems',
'AI', 'Debug', 'Timeline', 'Scripting', 'Screenshot', 'Utility'
])).optional().describe('Tool categories to load (see list_available_categories for options)'),
keywords: z.array(z.string()).optional().describe('Optional: filter by keywords (e.g. ["camera", "color"])'),
maxTools: z.number().optional().default(50).describe('Max tools to load (default: 50, max: 100)')
})
},
{
name: 'list_available_categories',
title: '📋 List Available Tool Categories',
description: 'Show all 31 Unity tool categories with counts and descriptions. RECOMMENDED: Call this first to discover available categories, then use select_tools to load the tools you need.',
inputSchema: z.object({})
},
{
name: 'search_tools',
title: '🔍 Search Unity Tools by Keyword',
description: 'Find Unity tools by keyword search. Supports single or multiple keywords (e.g. "camera", "particle effect", "material color"). Returns tool names, descriptions, categories, and relevance scores. Use this when you know what you want but not which category it belongs to.',
inputSchema: z.object({
query: z.string().describe('Search keyword(s) - single word or phrase'),
limit: z.number().optional().default(20).describe('Max results (default: 20)')
})
},
{
name: 'unity_get_scene_summary',
title: 'Get Scene Summary',
description: 'Get lightweight Unity scene overview (<200KB). Returns: scene name, GameObject count, cameras, lights, root GameObjects list.',
inputSchema: z.object({})
},
{
name: 'unity_get_gameobjects_list',
title: 'Get GameObjects List',
description: 'Get filtered list of GameObjects. Supports filters: layer, tag, name, activeOnly. Max 100 results.',
inputSchema: z.object({
layerFilter: z.string().optional(),
tagFilter: z.string().optional(),
nameFilter: z.string().optional(),
activeOnly: z.boolean().optional(),
maxCount: z.number().optional().default(100)
})
},
{
name: 'unity_get_gameobject_detail',
title: 'Get GameObject Detail',
description: 'Get detailed info for specific GameObject by name or instanceId.',
inputSchema: z.object({
nameOrId: z.string()
})
},
{
name: 'unity_undo',
title: 'Undo Unity Operation',
description: 'Undo last Unity operation',
inputSchema: z.object({})
},
{
name: 'unity_redo',
title: 'Redo Unity Operation',
description: 'Redo Unity operation',
inputSchema: z.object({})
}
];
// Currently active tools (starts with essential only)
let activeTools = [...essentialTools];
// Category metadata used for validation/default selection
const ALL_TOOL_CATEGORIES = [
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Cinemachine', 'Scene', 'GOAP', 'Audio', 'Input',
'VFX', 'Shader', 'Weather', 'TimeOfDay', 'Editor', 'Package', 'Build',
'Monitoring', 'AssetManagement', 'Optimization', 'Batch', 'GameSystems',
'AI', 'Debug', 'Timeline', 'Scripting', 'Screenshot', 'Utility', 'Other'
];
// Default categories to load when no parameters are supplied (covers core workflows)
const DEFAULT_TOOL_CATEGORIES = [
'GameObject', 'Transform', 'Material', 'Lighting', 'Camera', 'Physics',
'UI', 'Animation', 'Scene', 'Audio', 'Utility'
];
const CATEGORY_SET = new Set(ALL_TOOL_CATEGORIES);
function normalizeInputList(value) {
if (!value) {
return [];
}
if (Array.isArray(value)) {
return value
.map(item => (typeof item === 'string' ? item : String(item)).trim())
.filter(Boolean);
}
if (typeof value === 'string') {
return value
.split(/[,|]/)
.map(item => item.trim())
.filter(Boolean);
}
return [];
}
function normalizeCategories(value) {
return normalizeInputList(value).filter(item => CATEGORY_SET.has(item));
}
function normalizeKeywords(value) {
return normalizeInputList(value);
}
// Setup WebSocket server to accept Unity connections
function setupWebSocketServer() {
if (!wss) return;
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
if (isUnity) {
// Unity client connected
if (unityWs) {
unityWs.close();
}
unityWs = ws;
console.error('[Hub] Unity connected via WebSocket');
// Handle Unity responses (same format as index.js)
ws.on('message', (data) => {
try {
const response = JSON.parse(data.toString());
// Unity sends: { id, type: 'operation_result', data: { success: true/false }, content: "result" }
if (response.type === 'operation_result' && response.id) {
// Convert id to number like index.js does
const numericId = typeof response.id === 'string' ? parseInt(response.id) : response.id;
if (pendingRequests.has(numericId)) {
const { resolve, reject, timeout } = pendingRequests.get(numericId);
clearTimeout(timeout);
pendingRequests.delete(numericId);
// Match index.js format: resolve with content, not whole response
if (response.data && response.data.success) {
resolve(response.content);
} else {
reject(new Error(response.content || 'Unity command failed'));
}
}
}
} catch (error) {
console.error('[Hub] Failed to parse Unity response:', error.message);
}
});
ws.on('close', () => {
console.error('[Hub] Unity disconnected');
unityWs = null;
});
ws.on('error', (err) => {
console.error('[Hub] Unity WebSocket error:', err.message);
});
} else {
console.error('[Hub] Unknown client connected, closing connection');
ws.close();
}
});
}
// Helper function for delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Single command attempt to Unity
async function forwardToUnityOnce(command, params, id) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('timeout'));
}, 15000);
pendingRequests.set(id, { resolve, reject, timeout });
const message = JSON.stringify({
id,
type: 'tool_call',
tool: command,
command,
parameters: params || {}
});
unityWs.send(message);
});
}
// Forward command to Unity with auto-retry (enhanced for compilation waiting)
async function forwardToUnity(toolName, params) {
const MAX_RETRIES = 30; // 30 retries for very long compilations
const RETRY_DELAY = 10000; // 10 seconds between retries
const COMPILE_WAIT_DELAY = 10000; // 10 seconds when compiling (total max: ~5 min)
const command = toolName.startsWith('unity_') ? toolName.substring(6) : toolName;
let lastError = null;
let isLikelyCompiling = false;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
if (!unityWs || unityWs.readyState !== WebSocket.OPEN) {
lastError = new Error('Unity not connected');
isLikelyCompiling = true;
if (attempt < MAX_RETRIES) {
const waitTime = isLikelyCompiling ? COMPILE_WAIT_DELAY : RETRY_DELAY;
console.error(`[Hub] Unity not connected (attempt ${attempt}/${MAX_RETRIES}). Waiting ${waitTime/1000}s...`);
await sleep(waitTime);
continue;
}
break;
}
const id = ++requestId;
try {
const result = await forwardToUnityOnce(command, params, id);
// Format response for MCP client
const formattedResult = {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
if (attempt > 1) {
formattedResult.content[0].text += `\n[Note: Succeeded on retry attempt ${attempt}]`;
}
return formattedResult;
} catch (error) {
lastError = error;
if (error.message === 'timeout') {
isLikelyCompiling = true;
}
if (attempt < MAX_RETRIES) {
const waitTime = isLikelyCompiling ? COMPILE_WAIT_DELAY : RETRY_DELAY;
console.error(`[Hub] Command failed (attempt ${attempt}/${MAX_RETRIES}): ${error.message}. Retrying in ${waitTime/1000}s...`);
await sleep(waitTime);
}
}
}
throw new Error(`Unity not connected (after ${MAX_RETRIES} attempts). Unity may be recompiling - wait and try again.`);
}
// Handle select_tools - load tools dynamically
async function handleSelectTools(params = {}) {
const {
categories = [],
keywords = [],
maxTools = 50
} = params;
let normalizedCategories = normalizeCategories(categories);
const normalizedKeywords = normalizeKeywords(keywords);
if (normalizedCategories.length === 0 && normalizedKeywords.length === 0) {
normalizedCategories = [...DEFAULT_TOOL_CATEGORIES];
console.error('[Hub] select_tools called without parameters using default categories:', normalizedCategories.join(', '));
}
if (!toolRegistry) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Tool registry not available. Run: node scripts/generate-tool-registry.js'
}, null, 2)
}],
isError: true
};
}
let selectedToolNames = [];
// Filter by categories
if (normalizedCategories.length > 0) {
selectedToolNames = filterToolsByCategories(normalizedCategories, toolRegistry);
}
// Further filter by keywords using embedding search
if (normalizedKeywords.length > 0) {
const query = normalizedKeywords.join(' ');
const searchResults = await searchToolsByKeywords(query, 200, toolRegistry);
if (selectedToolNames.length > 0) {
// Intersect with category results
const searchNames = new Set(searchResults.map(r => r.name));
selectedToolNames = selectedToolNames.filter(name => searchNames.has(name));
} else {
// Use search results only
selectedToolNames = searchResults.map(r => r.name);
}
}
// Limit results
selectedToolNames = selectedToolNames.slice(0, Math.min(maxTools, 100));
// Convert tool names to full tool definitions
// Use passthrough schema to accept any parameters (forwarded to Unity)
const selectedTools = selectedToolNames.map(name => {
const meta = toolRegistry[name];
return {
name: name,
title: meta.title || name,
description: meta.description || '',
inputSchema: z.object({}).passthrough() // Accept any parameters, forward to Unity
};
});
// Update active tools (essential + selected)
activeTools = [...essentialTools, ...selectedTools];
console.error(`[Hub] Loaded ${selectedTools.length} tools. Total active: ${activeTools.length}`);
// Send notification to client that tool list has changed
// This triggers the client to call tools/list again
try {
await server.notification({
method: 'notifications/tools/list_changed'
});
console.error('[Hub] Sent tools/list_changed notification to client');
} catch (error) {
console.error('[Hub] Warning: Failed to send notification:', error.message);
// Continue anyway - client might not support notifications
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
loaded_tools: selectedTools.length,
total_active: activeTools.length,
categories: normalizedCategories,
message: 'Tools loaded. Client should refresh tool list automatically.',
tools: selectedTools.map(t => ({
name: t.name,
category: toolRegistry[t.name]?.category
}))
}, null, 2)
}]
};
}
// Handle list_available_categories
async function handleListCategories() {
const categories = getAvailableCategories();
return {
content: [{
type: 'text',
text: JSON.stringify({ categories }, null, 2)
}]
};
}
// Handle search_tools
async function handleSearchTools(params) {
const { query, limit = 20 } = params;
if (!toolRegistry) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'Tool registry not available'
}, null, 2)
}],
isError: true
};
}
const results = await searchToolsByKeywords(query, limit, toolRegistry);
return {
content: [{
type: 'text',
text: JSON.stringify({
query,
results: results.map(r => ({
name: r.name,
description: r.description,
category: r.category,
relevance: r.score.toFixed(3)
}))
}, null, 2)
}]
};
}
// MCP handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error(`[Hub] ListTools called - returning ${activeTools.length} tools`);
console.error(`[Hub] Essential tools: ${essentialTools.map(t => t.name).join(', ')}`);
return {
tools: activeTools.map(tool => {
// Convert Zod schema to JSON Schema format
let inputSchema;
if (tool.inputSchema && tool.inputSchema._def) {
// Extract properties from Zod schema
const zodShape = tool.inputSchema._def.shape?.() || {};
const properties = {};
const required = [];
for (const [key, value] of Object.entries(zodShape)) {
if (value._def) {
properties[key] = {
type: value._def.typeName === 'ZodString' ? 'string' :
value._def.typeName === 'ZodNumber' ? 'number' :
value._def.typeName === 'ZodBoolean' ? 'boolean' :
value._def.typeName === 'ZodArray' ? 'array' :
'string',
description: value._def.description || undefined
};
if (!value._def.isOptional) {
required.push(key);
}
}
}
inputSchema = {
type: 'object',
properties,
...(required.length > 0 ? { required } : {})
};
} else {
// Fallback for simple schemas
inputSchema = {
type: 'object',
properties: {},
additionalProperties: true
};
}
return {
name: tool.name,
description: tool.description,
inputSchema
};
})
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
console.error(`[Hub] Tool call: ${name}, args:`, JSON.stringify(args));
console.error(`[Hub] Available tools: ${activeTools.map(t => t.name).join(', ')}`);
try {
// Hub management tools
if (name === 'select_tools') {
return await handleSelectTools(args);
}
if (name === 'list_available_categories') {
return await handleListCategories();
}
if (name === 'search_tools') {
return await handleSearchTools(args);
}
// All other tools forward to Unity
return await forwardToUnity(name, args);
} catch (error) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: error.message
}, null, 2)
}],
isError: true
};
}
});
// Start server
async function main() {
// Start MCP server with Stdio transport (for VS Code)
const transport = new StdioServerTransport();
await server.connect(transport);
// Create WebSocket server for Unity connections
wss = new WebSocketServer({ server: httpServer });
setupWebSocketServer();
// Start HTTP server
const port = process.env.PORT || 8090;
httpServer.listen(port, () => {
console.error('[Hub] Unity Synaptic Hub Server v1.1.0 started');
console.error(`[Hub] WebSocket server listening on port ${port}`);
console.error(`[Hub] Essential tools loaded: ${essentialTools.length}`);
console.error('[Hub] Waiting for Unity connection and select_tools() calls...');
});
}
main().catch(error => {
console.error('[Hub] Fatal error:', error);
process.exit(1);
});
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 2a5cdf109495845349f291dc26520be0
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/hub-server.js
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: aee1b8376192c41a6bac6dca7574b820
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index-essential.js
uploadId: 920982
@@ -1,906 +0,0 @@
/**
* Synaptic AI Pro - Token SuperSave Mode
*
* Experimental: Only 6 meta-tools for 99% context reduction.
* Compatible with all MCP clients without dynamic tool loading support.
*
* Tools:
* 1. list_categories() - Show available tool categories
* 2. list_tools(category) - Show tools in a category with their schemas
* 3. execute(tool_name, params) - Run any tool by name
* 4. inspect(target, ...) - Dynamically inspect objects/components
* 5. modify(gameObject, component, properties) - Dynamically modify properties
* 6. create(type, ...) - Create objects, prefabs, components
*/
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const cors = require('cors');
const { z } = require('zod');
const { createServer } = require('./mcp-server');
const fs = require('fs');
const path = require('path');
const os = require('os');
const app = express();
app.use(cors());
app.use(express.json());
const server = http.createServer(app);
let wss = null;
let unityWebSocket = null;
let mcpServer = null;
// =====================================================
// Load Tool Registry from JSON file
// =====================================================
let TOOL_REGISTRY_RAW = {};
let CATEGORIES = {};
let ALL_TOOLS = {};
function loadToolRegistry() {
try {
const registryPath = path.join(__dirname, 'tool-registry.json');
const data = fs.readFileSync(registryPath, 'utf8');
TOOL_REGISTRY_RAW = JSON.parse(data);
// Build categories from registry
CATEGORIES = {};
ALL_TOOLS = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other').toLowerCase();
if (!CATEGORIES[category]) {
CATEGORIES[category] = {
description: `${toolData.category} tools`,
tools: {}
};
}
// Store tool info (use clean name without unity_ prefix as primary key)
const cleanName = toolName.replace(/^unity_/, '');
CATEGORIES[category].tools[cleanName] = {
fullName: toolName,
title: toolData.title,
description: toolData.description
};
// Only store clean name to avoid duplication
ALL_TOOLS[cleanName] = {
fullName: toolName,
category: category,
title: toolData.title,
description: toolData.description
};
}
console.error(`[SuperSave] Loaded ${Object.keys(TOOL_REGISTRY_RAW).length} tools from tool-registry.json`);
} catch (err) {
console.error('[SuperSave] Failed to load tool-registry.json:', err.message);
}
}
// Load on startup
loadToolRegistry();
// =====================================================
// WebSocket Setup (same as index.js)
// =====================================================
function setupWebSocketHandlers() {
if (!wss) return;
// Surface server-level errors (ECONNRESET etc.) that would otherwise
// disappear into the void.
wss.on('error', (err) => {
console.error('[SuperSave] WSS error:', err && err.message ? err.message : err);
});
wss.on('connection', (ws, req) => {
const isUnity = req.headers['x-client-type'] === 'unity' || req.url === '/unity';
// Per-socket error handler — without this, send() failures (peer gone,
// backpressure, etc.) crash silently and the caller just hits the 60s
// timeout with no clue why.
ws.on('error', (err) => {
console.error('[SuperSave] WS socket error:', err && err.message ? err.message : err);
});
if (isUnity || !req.url.includes('mcp')) {
if (unityWebSocket) {
unityWebSocket.close();
}
unityWebSocket = ws;
ws.on('message', async (message) => {
try {
const data = JSON.parse(message);
// Handle shutdown request from new process trying to take over
if (data.type === 'shutdown') {
console.error('[SuperSave] Received shutdown request from new process, shutting down...');
shutdownServer();
return;
}
const responseId = data.id || data.operationId;
if ((data.type === 'operation_result' || data.type === 'operation_response') && responseId) {
const numericId = typeof responseId === 'string' ? parseInt(responseId) : responseId;
if (pendingRequests.has(numericId)) {
const { resolve, reject, timeout } = pendingRequests.get(numericId);
clearTimeout(timeout);
pendingRequests.delete(numericId);
if (data.success !== false) {
resolve(data.content || data.result);
} else {
reject(new Error(data.content || data.error || 'Unity command failed'));
}
}
}
} catch (e) {}
});
ws.on('close', () => {
unityWebSocket = null;
});
}
});
}
// Unity command helper
const pendingRequests = new Map();
let requestId = 0;
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function sendUnityCommandOnce(command, params, id) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pendingRequests.delete(id);
reject(new Error('timeout'));
}, 60000);
pendingRequests.set(id, { resolve, reject, timeout });
const message = JSON.stringify({
type: 'unity_operation',
command: command,
parameters: {
...params,
operationId: id.toString()
}
});
// Confirm the socket is open before pushing — without this we have
// raced an in-flight close() and the message disappears.
if (!unityWebSocket || unityWebSocket.readyState !== 1 /* OPEN */) {
clearTimeout(timeout);
pendingRequests.delete(id);
const state = unityWebSocket ? unityWebSocket.readyState : 'null';
return reject(new Error(`unityWebSocket not open (readyState=${state})`));
}
// send() with callback so write failures surface immediately instead
// of bleeding into the 60s timeout. ESC-0102 root-cause hunt.
unityWebSocket.send(message, (err) => {
if (err) {
clearTimeout(timeout);
pendingRequests.delete(id);
console.error('[SuperSave] send failed:', err.message);
reject(err);
}
});
});
}
async function sendUnityCommand(command, params = {}) {
const MAX_RETRIES = 30;
const RETRY_DELAY = 10000;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
if (!unityWebSocket || unityWebSocket.readyState !== WebSocket.OPEN) {
if (attempt < MAX_RETRIES) {
console.error(`[SuperSave] Unity not connected (attempt ${attempt}/${MAX_RETRIES}). Waiting...`);
await sleep(RETRY_DELAY);
continue;
}
throw new Error('Unity not connected');
}
const id = ++requestId;
try {
const result = await sendUnityCommandOnce(command, params, id);
return result;
} catch (error) {
if (attempt < MAX_RETRIES) {
console.error(`[SuperSave] Command failed (attempt ${attempt}): ${error.message}`);
await sleep(RETRY_DELAY);
}
}
}
throw new Error('Unity not connected after retries');
}
// =====================================================
// MCP Server with 3 Meta-Tools
// =====================================================
async function setupMCPServer() {
mcpServer = createServer();
// ===== META-TOOL 1: list_categories =====
mcpServer.registerTool('list_categories', {
title: 'List Tool Categories',
description: 'List all available tool categories. Use this first to discover what tools are available.',
inputSchema: z.object({})
}, async () => {
const categories = Object.entries(CATEGORIES).map(([name, data]) => ({
name,
description: data.description,
toolCount: Object.keys(data.tools).length
}));
const totalTools = Object.keys(TOOL_REGISTRY_RAW).length;
return {
content: [{
type: 'text',
text: `Available Categories (${categories.length} categories, ${totalTools} total tools):\n\n` +
categories.map(c => `${c.name} (${c.toolCount} tools)\n ${c.description}`).join('\n\n') +
'\n\nUse list_tools(category) to see tools in a specific category.'
}]
};
});
// ===== META-TOOL 2: list_tools =====
mcpServer.registerTool('list_tools', {
title: 'List Tools in Category',
description: 'List all tools in a specific category with their parameters. Use this to learn how to use specific tools.',
inputSchema: z.object({
category: z.string().describe('Category name (e.g., "gameobject", "material", "lighting")')
})
}, async (params) => {
const category = params.category.toLowerCase();
if (!CATEGORIES[category]) {
const availableCategories = Object.keys(CATEGORIES).join(', ');
return {
content: [{
type: 'text',
text: `Unknown category: "${category}"\n\nAvailable categories: ${availableCategories}`
}]
};
}
const categoryData = CATEGORIES[category];
const tools = Object.entries(categoryData.tools).map(([name, data]) => {
return `${name} (${data.fullName})\n ${data.title}\n ${data.description}`;
});
return {
content: [{
type: 'text',
text: `Category: ${category}\n${categoryData.description}\n\nTools (${tools.length}):\n\n${tools.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
}]
};
});
// ===== META-TOOL 2.5: search_tools =====
mcpServer.registerTool('search_tools', {
title: 'Search Tools',
description: 'Search for tools by keyword in name, title, or description. Use this when you don\'t know the exact category.',
inputSchema: z.object({
query: z.string().describe('Search keyword (e.g., "material", "camera", "physics")'),
category: z.string().optional().describe('Optional: filter by category'),
limit: z.number().optional().default(20).describe('Max results (default: 20)')
})
}, async (params) => {
const query = (params.query || '').toLowerCase();
const categoryFilter = params.category?.toLowerCase();
const limit = params.limit || 20;
const results = [];
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const title = toolData.title || toolName;
const description = toolData.description || '';
const category = (toolData.category || 'Other').toLowerCase();
// Category filter
if (categoryFilter && category !== categoryFilter) continue;
// If no query, return all (with limit)
if (!query) {
results.push({ name: toolName, title, description, category: toolData.category, score: 0 });
if (results.length >= limit) break;
continue;
}
// Calculate score
let score = 0;
if (toolName.toLowerCase().includes(query)) score += 100;
if (title.toLowerCase().includes(query)) score += 80;
if (description.toLowerCase().includes(query)) score += 40;
if (score > 0) {
results.push({ name: toolName, title, description, category: toolData.category, score });
}
}
// Sort by score and limit
const sorted = results
.sort((a, b) => b.score - a.score)
.slice(0, limit);
if (sorted.length === 0) {
return {
content: [{
type: 'text',
text: `No tools found for query: "${params.query}"\n\nTry different keywords or use list_categories to see available categories.`
}]
};
}
const toolList = sorted.map(t => `${t.name}\n ${t.title} [${t.category}]\n ${t.description.slice(0, 100)}${t.description.length > 100 ? '...' : ''}`);
return {
content: [{
type: 'text',
text: `Found ${sorted.length} tools for "${params.query}":\n\n${toolList.join('\n\n')}\n\nUse execute(tool_name, params) to run a tool.`
}]
};
});
// ===== META-TOOL 2.6: get_tools_reference =====
mcpServer.registerTool('get_tools_reference', {
title: 'Get Full Tools Reference',
description: 'Get complete reference of ALL tools in Markdown format. Use this once at the start of a session to have full tool knowledge without repeated search_tools calls. Saves tokens by eliminating multiple tool discovery calls.',
inputSchema: z.object({
lang: z.enum(['en', 'jp']).optional().default('en').describe('Language for descriptions (en/jp)'),
category: z.string().optional().describe('Optional: filter by specific category'),
format: z.enum(['markdown', 'compact']).optional().default('markdown').describe('Output format: markdown (detailed) or compact (name + description only)')
})
}, async (params) => {
const lang = params.lang || 'en';
const categoryFilter = params.category?.toLowerCase();
const format = params.format || 'markdown';
// Group tools by category
const byCategory = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other');
const categoryLower = category.toLowerCase();
if (categoryFilter && categoryLower !== categoryFilter) continue;
if (!byCategory[category]) {
byCategory[category] = [];
}
byCategory[category].push({ name: toolName, ...toolData });
}
const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
let output = '';
if (format === 'markdown') {
output = `# Synaptic AI Pro - Tools Reference\n`;
output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category} (${tools.length} tools)\n\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `### ${tool.name}\n`;
output += `${tool.description || tool.title || ''}\n`;
// Add inputSchema info if available
if (tool.inputSchema?.properties) {
const props = Object.entries(tool.inputSchema.properties);
if (props.length > 0) {
output += `**Parameters:**\n`;
for (const [propName, propData] of props) {
const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
const type = propData.type || 'any';
const desc = propData.description || '';
output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
}
}
}
output += '\n';
}
}
} else {
// Compact format
output = `# Tools Reference (${totalTools} tools)\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category}\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
}
output += '\n';
}
}
output += `\n---\nUse execute(tool_name, params) to run any tool.`;
return {
content: [{
type: 'text',
text: output
}]
};
});
// ===== MCP RESOURCES: Tools Reference =====
// Generate tools reference markdown from tool-registry.json
function generateToolsReference(format = 'compact') {
const byCategory = {};
for (const [toolName, toolData] of Object.entries(TOOL_REGISTRY_RAW)) {
const category = (toolData.category || 'Other');
if (!byCategory[category]) {
byCategory[category] = [];
}
byCategory[category].push({ name: toolName, ...toolData });
}
const totalTools = Object.values(byCategory).reduce((sum, tools) => sum + tools.length, 0);
let output = '';
if (format === 'markdown') {
output = `# Synaptic AI Pro - Tools Reference\n`;
output += `Total: ${totalTools} tools in ${Object.keys(byCategory).length} categories\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category} (${tools.length} tools)\n\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `### ${tool.name}\n`;
output += `${tool.description || tool.title || ''}\n`;
if (tool.inputSchema?.properties) {
const props = Object.entries(tool.inputSchema.properties);
if (props.length > 0) {
output += `**Parameters:**\n`;
for (const [propName, propData] of props) {
const required = tool.inputSchema.required?.includes(propName) ? ' (required)' : '';
const type = propData.type || 'any';
const desc = propData.description || '';
output += `- \`${propName}\`: ${type}${required} - ${desc}\n`;
}
}
}
output += '\n';
}
}
} else {
// Compact format
output = `# Tools Reference (${totalTools} tools)\n\n`;
for (const [category, tools] of Object.entries(byCategory).sort((a, b) => a[0].localeCompare(b[0]))) {
output += `## ${category}\n`;
for (const tool of tools.sort((a, b) => a.name.localeCompare(b.name))) {
output += `- ${tool.name}: ${(tool.description || tool.title || '').slice(0, 80)}\n`;
}
output += '\n';
}
}
output += `\n---\nUse execute(tool_name, params) to run any tool.`;
return output;
}
// Register MCP Resources for tools reference (for prompt caching)
mcpServer.registerResource('synaptic://tools/reference', {
title: 'Tools Reference (Compact)',
description: 'Complete list of all Unity tools in compact format. Add to context for efficient tool discovery.',
mimeType: 'text/markdown'
}, async () => {
return {
contents: [{
uri: 'synaptic://tools/reference',
mimeType: 'text/markdown',
text: generateToolsReference('compact')
}]
};
});
mcpServer.registerResource('synaptic://tools/reference/full', {
title: 'Tools Reference (Full)',
description: 'Complete list of all Unity tools with full parameter details.',
mimeType: 'text/markdown'
}, async () => {
return {
contents: [{
uri: 'synaptic://tools/reference/full',
mimeType: 'text/markdown',
text: generateToolsReference('markdown')
}]
};
});
// ===== META-TOOL 3: execute =====
mcpServer.registerTool('execute', {
title: 'Execute Tool',
description: 'Execute any Unity tool by name. Use list_tools(category) first to see available tools and their parameters.',
inputSchema: z.object({
tool: z.string().describe('Tool name to execute (e.g., "create_gameobject", "set_transform")'),
params: z.any().optional().describe('Parameters as JSON object {"name":"value"}')
})
}, async (params) => {
const toolName = params.tool;
let toolParams = params.params || {};
// Handle case where params is passed as string (e.g., '{"name":"x"}')
if (typeof toolParams === 'string') {
try {
toolParams = JSON.parse(toolParams);
} catch (e) {
// Try to parse key=value format (e.g., 'name=MyCube')
const keyValueMatch = toolParams.match(/^(\w+)=(.+)$/);
if (keyValueMatch) {
toolParams = { [keyValueMatch[1]]: keyValueMatch[2] };
} else {
// Treat as single value if it's just a plain string
toolParams = {};
}
}
}
// Normalize tool name - strip unity_ prefix if present
const strippedName = toolName.startsWith('unity_') ? toolName.substring(6) : toolName;
const fullName = `unity_${strippedName}`;
// Check if tool exists in registry
const toolInfo = ALL_TOOLS[strippedName];
if (!toolInfo) {
// Find similar tool names for helpful error message
const allToolNames = Object.keys(ALL_TOOLS);
const similar = allToolNames.filter(t =>
t.includes(strippedName) || strippedName.includes(t) ||
t.split('_').some(part => strippedName.includes(part))
).slice(0, 5);
let errorMsg = `Unknown tool: "${toolName}"`;
if (similar.length > 0) {
errorMsg += `\n\nDid you mean: ${similar.join(', ')}?`;
}
errorMsg += `\n\nUse list_categories() to see available categories, then list_tools(category) to see tools.`;
return {
content: [{
type: 'text',
text: errorMsg
}]
};
}
// Get the command name (without unity_ prefix) for Unity
// Use lowercase - Unity's ConvertCommandToOperationType expects lowercase
const commandName = strippedName.toLowerCase();
try {
const result = await sendUnityCommand(commandName, toolParams);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
// Detailed error message
let errorDetail = `Error executing "${strippedName}":\n`;
errorDetail += ` Message: ${error.message}\n`;
if (error.message.includes('not connected')) {
errorDetail += `\nTroubleshooting:\n`;
errorDetail += ` 1. Check Unity Editor is running\n`;
errorDetail += ` 2. Verify Synaptic AI Pro is connected (check Console)\n`;
errorDetail += ` 3. Try restarting the MCP server\n`;
} else if (error.message.includes('timeout')) {
errorDetail += `\nThe command timed out. Unity may be:\n`;
errorDetail += ` - Compiling scripts\n`;
errorDetail += ` - Processing a heavy operation\n`;
errorDetail += ` - Not responding\n`;
}
errorDetail += `\nTool info: ${toolInfo.title} (${toolInfo.category})`;
return {
content: [{
type: 'text',
text: errorDetail
}]
};
}
});
// ===== META-TOOL 3.5: run_csharp =====
// Arbitrary C# execution escape-hatch (equivalent of Blender's run_python).
// Promoted to a top-level meta-tool so small local LLMs don't have to go
// through the execute({tool, params}) two-level nest.
mcpServer.registerTool('run_csharp', {
title: 'Run C# Code',
description: 'Execute arbitrary C# code against the running Unity Editor (equivalent of Blender run_python). UnityEngine / UnityEditor / System.Linq / Newtonsoft.Json are pre-imported. Use this when no dedicated tool covers the operation. Does NOT trigger AssemblyReload so the connection stays alive.\n\nReturn value: end the snippet with `return X;` to capture X into the `result` field (prefix statements like `var x = ...;` execute first). A bare expression without trailing `;` is also accepted. Side-effect-only snippets return `result: null`. Debug.Log / LogWarning / LogError are captured into `output`.\n\nWORKS: GameObject / Transform / Component manipulation, AssetDatabase, Selection, EditorApplication, scene/asset queries via `FindObjectsByType<T>()` and other generic METHODS, generic method extension calls (`GetComponent<T>()`), arrays + foreach + LINQ-like loops, string interpolation, math, multi-statement bodies.\n\nKNOWN LIMITATION: Unity Mono.CSharp interactive parser cannot instantiate generic TYPES — `new List<int>()`, `new Dictionary<K,V>()`, `new HashSet<T>()` etc. silently return `result: null`. Workarounds: use plain arrays (`new int[] {1,2,3}`), `System.Collections.ArrayList`, or invoke generic helper methods that already exist (e.g. `FindObjectsByType<GameObject>(...)`). Generic method calls themselves are fine; only `new T<U>()` is blocked. LINQ chains that infer `IEnumerable<T>` may also fail — use `foreach` instead.',
inputSchema: z.object({
code: z.string().describe('C# code. End with `return X;` to capture X into `result`. A bare expression (no trailing `;`) also returns its value. Pre-imported: System, System.Linq, System.Collections.Generic, UnityEngine, UnityEditor, Newtonsoft.Json. AVOID `new List<int>()` / `new Dictionary<K,V>()` style generic instantiation (Mono parser limitation) — use arrays or ArrayList instead.')
})
}, async (params) => {
try {
const result = await sendUnityCommand('run_csharp', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `run_csharp failed: ${error.message}`
}]
};
}
});
// ===== META-TOOL 4: inspect =====
mcpServer.registerTool('inspect', {
title: 'Inspect Unity Objects',
description: 'Dynamically inspect any Unity object, component, scene, or project assets. Use this to discover what properties are available before modifying them.',
inputSchema: z.object({
target: z.enum(['gameobject', 'component', 'scene', 'project', 'prefabs', 'hierarchy'])
.describe('What to inspect: gameobject (properties/components), component (all serialized fields), scene (current scene info), project (project structure), prefabs (search prefabs), hierarchy (scene hierarchy)'),
name: z.string().optional().describe('GameObject name (for gameobject/component targets)'),
component: z.string().optional().describe('Component type to inspect (e.g., "Camera", "CinemachineVirtualCamera")'),
path: z.string().optional().describe('Asset path filter for project/prefabs (e.g., "Assets/Prefabs/*")'),
depth: z.number().optional().default(2).describe('Hierarchy depth for nested inspection (default: 2)')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_inspect', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Inspect failed: ${error.message}\n\nTips:\n- For gameobject: provide "name"\n- For component: provide "name" and "component"\n- For prefabs: provide "path" with wildcards (e.g., "Assets/**/*.prefab")`
}]
};
}
});
// ===== META-TOOL 5: modify =====
mcpServer.registerTool('modify', {
title: 'Modify Unity Objects',
description: 'Dynamically modify any property of a Unity component using property paths. Use inspect() first to discover available properties.',
inputSchema: z.object({
gameObject: z.string().describe('GameObject name'),
component: z.string().describe('Component type (e.g., "Transform", "Camera", "CinemachineVirtualCamera")'),
properties: z.record(z.any()).describe('Property paths and values as key-value pairs (e.g., {"m_Lens.FieldOfView": 60, "m_Priority": 10})'),
createIfMissing: z.boolean().optional().default(false).describe('Add component if it does not exist')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_modify', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Modify failed: ${error.message}\n\nTips:\n- Use inspect(target:"component") first to see available property paths\n- Nested properties use dot notation: "m_Lens.FieldOfView"\n- Array elements: "m_Materials.Array.data[0]"`
}]
};
}
});
// ===== META-TOOL 6: create =====
mcpServer.registerTool('create', {
title: 'Create Unity Objects',
description: 'Create GameObjects, instantiate prefabs, load scenes, or add components. Universal creation tool.',
inputSchema: z.object({
type: z.enum(['gameobject', 'prefab', 'scene', 'component'])
.describe('What to create: gameobject (empty or primitive), prefab (instantiate from asset), scene (load scene), component (add to existing object)'),
// For gameobject
name: z.string().optional().describe('Name for new GameObject'),
primitive: z.enum(['empty', 'cube', 'sphere', 'cylinder', 'plane', 'capsule', 'quad']).optional().describe('Primitive type (for gameobject)'),
// For prefab
asset: z.string().optional().describe('Asset path for prefab (e.g., "Assets/Prefabs/Enemy.prefab")'),
// For scene
scene: z.string().optional().describe('Scene name or path to load'),
additive: z.boolean().optional().default(false).describe('Load scene additively (for scene type)'),
// For component
gameObject: z.string().optional().describe('Target GameObject (for component type)'),
component: z.string().optional().describe('Component type to add (for component type)'),
// Common
parent: z.string().optional().describe('Parent GameObject name'),
position: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('World position'),
rotation: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Euler rotation'),
scale: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe('Local scale')
})
}, async (params) => {
try {
const result = await sendUnityCommand('dynamic_create', params);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Create failed: ${error.message}\n\nExamples:\n- GameObject: {type:"gameobject", name:"Player", primitive:"capsule"}\n- Prefab: {type:"prefab", asset:"Assets/Prefabs/Enemy.prefab", position:{x:0,y:0,z:0}}\n- Scene: {type:"scene", scene:"Level2", additive:true}\n- Component: {type:"component", gameObject:"Player", component:"Rigidbody"}`
}]
};
}
});
// Start MCP server
await mcpServer.start();
}
// =====================================================
// Main Entry Point
// =====================================================
// Send shutdown request to prior process on same port
async function requestShutdownFromPriorProcess(port) {
return new Promise((resolve) => {
try {
const ws = new WebSocket(`ws://localhost:${port}`);
const timeout = setTimeout(() => {
ws.close();
resolve(false);
}, 2000);
ws.on('open', () => {
ws.send(JSON.stringify({ type: 'shutdown' }));
clearTimeout(timeout);
setTimeout(() => {
ws.close();
resolve(true);
}, 500);
});
ws.on('error', () => {
clearTimeout(timeout);
resolve(false);
});
} catch (e) {
resolve(false);
}
});
}
// Try to start server with retry logic
function startServerWithRetry(port, maxRetries = 5, retryDelay = 1000) {
return new Promise((resolve, reject) => {
let attempt = 0;
const tryListen = () => {
attempt++;
console.error(`[SuperSave] Attempting to listen on port ${port} (attempt ${attempt}/${maxRetries})`);
const onError = async (err) => {
server.removeListener('error', onError);
if (err.code === 'EADDRINUSE') {
console.error(`[SuperSave] Port ${port} in use`);
// Check if existing server is healthy (another Claude session may be using it)
try {
const http = require('http');
const healthCheck = await new Promise((res, rej) => {
const req = http.get(`http://localhost:${port}/health`, { timeout: 2000 }, (resp) => {
let data = '';
resp.on('data', chunk => data += chunk);
resp.on('end', () => res(data));
});
req.on('error', rej);
req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
});
// Existing server is healthy - don't kill it, just skip server binding
console.error(`[SuperSave] Existing healthy server found on port ${port} - sharing connection`);
resolve();
return;
} catch (e) {
// Existing server is dead - safe to take over
}
if (attempt === 1) {
// First retry: try to shutdown prior process
console.error(`[SuperSave] Sending shutdown request to prior process...`);
await requestShutdownFromPriorProcess(port);
}
if (attempt < maxRetries) {
console.error(`[SuperSave] Retrying in ${retryDelay}ms...`);
setTimeout(tryListen, retryDelay);
} else {
reject(new Error(`Failed to bind to port ${port} after ${maxRetries} attempts`));
}
} else {
reject(err);
}
};
server.once('error', onError);
server.listen(port, () => {
server.removeListener('error', onError);
console.error(`[SuperSave] Token SuperSave Mode started on port ${port}`);
console.error(`[SuperSave] Only 6 meta-tools loaded (99% context reduction)`);
resolve();
});
};
tryListen();
});
}
async function main() {
const PORT = process.env.PORT || 8090;
// Start WebSocket server
wss = new WebSocket.Server({ server });
setupWebSocketHandlers();
// Setup and start MCP
await setupMCPServer();
// Start HTTP server with retry logic for EADDRINUSE
await startServerWithRetry(PORT);
}
// Shutdown handler
function shutdownServer() {
if (unityWebSocket && unityWebSocket.readyState === WebSocket.OPEN) {
unityWebSocket.close();
}
if (wss) {
wss.close();
}
if (server && server.listening) {
server.close(() => {
process.exit(0);
});
} else {
process.exit(0);
}
setTimeout(() => {
process.exit(1);
}, 5000);
}
process.on('SIGINT', shutdownServer);
process.on('SIGTERM', shutdownServer);
process.stdin.on('close', () => {
shutdownServer();
});
process.on('uncaughtException', (error) => {
// Log EADDRINUSE and other critical errors
if (error.code === 'EADDRINUSE') {
console.error(`[SuperSave] uncaughtException: EADDRINUSE - port already in use`);
} else {
console.error(`[SuperSave] uncaughtException: ${error.message}`);
}
});
process.on('unhandledRejection', (reason, promise) => {
console.error(`[SuperSave] unhandledRejection: ${reason}`);
});
main().catch(err => {
console.error('[SuperSave] Fatal error:', err);
process.exit(1);
});
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 23e712a62571842ceb12739ea240a053
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index-supersave.js
uploadId: 920982
File diff suppressed because it is too large Load Diff
@@ -1,14 +0,0 @@
fileFormatVersion: 2
guid: 0a4175e1c6a18427ba16d7e58e1173fa
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 336030
packageName: Synaptic AI Pro - Natural Language Control for Unity
packageVersion: 1.2.23
assetPath: Assets/Synaptic AI Pro/MCPServer/index.js
uploadId: 920982
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a2e176bd848a84855b6f6c338cf3fcfc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More