using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using MCPForUnity.Editor.Helpers; using UnityEngine; using UnityEditor; #if UNITY_VFX_GRAPH //Please enable the symbol in the project settings for VisualEffectGraph to work using UnityEngine.VFX; #endif namespace MCPForUnity.Editor.Tools.Vfx { /// /// Tool for managing Unity VFX components: /// - ParticleSystem (legacy particle effects) /// - Visual Effect Graph (modern GPU particles, currently only support HDRP, other SRPs may not work) /// - LineRenderer (lines, bezier curves, shapes) /// - TrailRenderer (motion trails) /// /// COMPONENT REQUIREMENTS: /// - particle_* actions require ParticleSystem component on target GameObject /// - vfx_* actions require VisualEffect component (+ com.unity.visualeffectgraph package) /// - line_* actions require LineRenderer component /// - trail_* actions require TrailRenderer component /// /// TARGETING: /// Use 'target' parameter with optional 'searchMethod': /// - by_name (default): "Fire" finds first GameObject named "Fire" /// - by_path: "Effects/Fire" finds GameObject at hierarchy path /// - by_id: "12345" finds GameObject by instance ID (most reliable) /// - by_tag: "Enemy" finds first GameObject with tag /// /// AUTOMATIC MATERIAL ASSIGNMENT: /// VFX components (ParticleSystem, LineRenderer, TrailRenderer) automatically receive /// appropriate default materials based on the active rendering pipeline when no material /// is explicitly specified: /// - Built-in Pipeline: Uses Unity's built-in Default-Particle.mat and Default-Line.mat /// - URP/HDRP: Creates materials with pipeline-appropriate unlit shaders /// - Materials are cached to avoid recreation /// - Explicit materialPath parameter always overrides auto-assignment /// - Auto-assigned materials are logged for transparency /// /// AVAILABLE ACTIONS: /// /// ParticleSystem (particle_*): /// - particle_get_info: Get system info and current state /// - particle_set_main: Set main module (duration, looping, startLifetime, startSpeed, startSize, startColor, gravityModifier, maxParticles, simulationSpace, playOnAwake, etc.) /// - particle_set_emission: Set emission module (rateOverTime, rateOverDistance) /// - particle_set_shape: Set shape module (shapeType, radius, angle, arc, position, rotation, scale) /// - particle_set_color_over_lifetime: Set color gradient over particle lifetime /// - particle_set_size_over_lifetime: Set size curve over particle lifetime /// - particle_set_velocity_over_lifetime: Set velocity (x, y, z, speedModifier, space) /// - particle_set_noise: Set noise turbulence (strength, frequency, scrollSpeed, damping, octaveCount, quality) /// - particle_set_renderer: Set renderer (renderMode, material, sortMode, minParticleSize, maxParticleSize, etc.) /// - particle_enable_module: Enable/disable modules by name /// - particle_play/stop/pause/restart/clear: Playback control (withChildren optional) /// - particle_add_burst: Add emission burst (time, count, cycles, interval, probability) /// - particle_clear_bursts: Clear all bursts /// /// Visual Effect Graph (vfx_*): /// Asset Management: /// - vfx_create_asset: Create new VFX asset file (assetName, folderPath, template, overwrite) /// - vfx_assign_asset: Assign VFX asset to VisualEffect component (target, assetPath) /// - vfx_list_templates: List available VFX templates in project and packages /// - vfx_list_assets: List all VFX assets (folder, search filters) /// Runtime Control: /// - vfx_get_info: Get VFX info including exposed parameters /// - vfx_set_float/int/bool: Set exposed scalar parameters (parameter, value) /// - vfx_set_vector2/vector3/vector4: Set exposed vector parameters (parameter, value as array) /// - vfx_set_color: Set exposed color (parameter, color as [r,g,b,a]) /// - vfx_set_gradient: Set exposed gradient (parameter, gradient) /// - vfx_set_texture: Set exposed texture (parameter, texturePath) /// - vfx_set_mesh: Set exposed mesh (parameter, meshPath) /// - vfx_set_curve: Set exposed animation curve (parameter, curve) /// - vfx_send_event: Send event with attributes (eventName, position, velocity, color, size, lifetime) /// - vfx_play/stop/pause/reinit: Playback control /// - vfx_set_playback_speed: Set playback speed multiplier (playRate) /// - vfx_set_seed: Set random seed (seed, resetSeedOnPlay) /// /// LineRenderer (line_*): /// - line_get_info: Get line info (position count, width, color, etc.) /// - line_set_positions: Set all positions (positions as [[x,y,z], ...]) /// - line_add_position: Add position at end (position as [x,y,z]) /// - line_set_position: Set specific position (index, position) /// - line_set_width: Set width (width, startWidth, endWidth, widthCurve, widthMultiplier) /// - line_set_color: Set color (color, gradient, startColor, endColor) /// - line_set_material: Set material (materialPath) /// - line_set_properties: Set renderer properties (loop, useWorldSpace, alignment, textureMode, numCornerVertices, numCapVertices, etc.) /// - line_clear: Clear all positions /// Shape Creation: /// - line_create_line: Create simple line (start, end, segments) /// - line_create_circle: Create circle (center, radius, segments, normal) /// - line_create_arc: Create arc (center, radius, startAngle, endAngle, segments, normal) /// - line_create_bezier: Create Bezier curve (start, end, controlPoint1, controlPoint2, segments) /// /// TrailRenderer (trail_*): /// - trail_get_info: Get trail info /// - trail_set_time: Set trail duration (time) /// - trail_set_width: Set width (width, startWidth, endWidth, widthCurve, widthMultiplier) /// - trail_set_color: Set color (color, gradient, startColor, endColor) /// - trail_set_material: Set material (materialPath) /// - trail_set_properties: Set properties (minVertexDistance, autodestruct, emitting, alignment, textureMode, etc.) /// - trail_clear: Clear trail /// - trail_emit: Emit point at current position (Unity 2021.1+) /// /// COMMON PARAMETERS: /// - target (string): GameObject identifier /// - searchMethod (string): "by_id" | "by_name" | "by_path" | "by_tag" | "by_layer" /// - materialPath (string): Asset path to material (e.g., "Assets/Materials/Fire.mat") /// - color (array): Color as [r, g, b, a] with values 0-1 /// - position (array): 3D position as [x, y, z] /// - gradient (object): {colorKeys: [{color: [r,g,b,a], time: 0-1}], alphaKeys: [{alpha: 0-1, time: 0-1}]} /// - curve (object): {keys: [{time: 0-1, value: number, inTangent: number, outTangent: number}]} /// /// For full parameter details, refer to Unity documentation for each component type. /// [McpForUnityTool("manage_vfx", AutoRegister = false)] public static class ManageVFX { private static readonly Dictionary ParamAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "size_over_lifetime", "size" }, { "start_color_line", "startColor" }, { "sorting_layer_id", "sortingLayerID" }, { "material", "materialPath" }, }; private static JObject NormalizeParams(JObject source) { if (source == null) { return new JObject(); } var normalized = new JObject(); var properties = ExtractProperties(source); if (properties != null) { foreach (var prop in properties.Properties()) { normalized[NormalizeKey(prop.Name, true)] = NormalizeToken(prop.Value); } } foreach (var prop in source.Properties()) { if (string.Equals(prop.Name, "properties", StringComparison.OrdinalIgnoreCase)) { continue; } normalized[NormalizeKey(prop.Name, true)] = NormalizeToken(prop.Value); } return normalized; } private static JObject ExtractProperties(JObject source) { if (source == null) { return null; } if (!source.TryGetValue("properties", StringComparison.OrdinalIgnoreCase, out var token)) { return null; } if (token == null || token.Type == JTokenType.Null) { return null; } if (token is JObject obj) { return obj; } if (token.Type == JTokenType.String) { try { return JToken.Parse(token.ToString()) as JObject; } catch (JsonException ex) { throw new JsonException( $"Failed to parse 'properties' JSON string. Raw value: {token}", ex); } } return null; } private static string NormalizeKey(string key, bool allowAliases) { if (string.IsNullOrEmpty(key)) { return key; } if (string.Equals(key, "action", StringComparison.OrdinalIgnoreCase)) { return "action"; } if (allowAliases && ParamAliases.TryGetValue(key, out var alias)) { return alias; } if (key.IndexOf('_') >= 0) { return ToCamelCase(key); } return key; } private static JToken NormalizeToken(JToken token) { if (token == null) { return null; } if (token is JObject obj) { var normalized = new JObject(); foreach (var prop in obj.Properties()) { normalized[NormalizeKey(prop.Name, false)] = NormalizeToken(prop.Value); } return normalized; } if (token is JArray array) { var normalized = new JArray(); foreach (var item in array) { normalized.Add(NormalizeToken(item)); } return normalized; } return token; } private static string ToCamelCase(string key) { if (string.IsNullOrEmpty(key) || key.IndexOf('_') < 0) { return key; } var parts = key.Split('_'); if (parts.Length == 0) { return key; } var first = parts[0]; var rest = string.Concat(parts.Skip(1).Select(part => string.IsNullOrEmpty(part) ? "" : char.ToUpperInvariant(part[0]) + part.Substring(1))); return first + rest; } public static object HandleCommand(JObject @params) { JObject normalizedParams = NormalizeParams(@params); string action = normalizedParams["action"]?.ToString(); if (string.IsNullOrEmpty(action)) { return new { success = false, message = "Action is required" }; } try { string actionLower = action.ToLowerInvariant(); // Route to appropriate handler based on action prefix if (actionLower == "ping") { return new { success = true, tool = "manage_vfx", components = new[] { "ParticleSystem", "VisualEffect", "LineRenderer", "TrailRenderer" } }; } // ParticleSystem actions (particle_*) if (actionLower.StartsWith("particle_")) { return HandleParticleSystemAction(normalizedParams, actionLower.Substring(9)); } // VFX Graph actions (vfx_*) if (actionLower.StartsWith("vfx_")) { return HandleVFXGraphAction(normalizedParams, actionLower.Substring(4)); } // LineRenderer actions (line_*) if (actionLower.StartsWith("line_")) { return HandleLineRendererAction(normalizedParams, actionLower.Substring(5)); } // TrailRenderer actions (trail_*) if (actionLower.StartsWith("trail_")) { return HandleTrailRendererAction(normalizedParams, actionLower.Substring(6)); } return new { success = false, message = $"Unknown action: {action}. Actions must be prefixed with: particle_, vfx_, line_, or trail_" }; } catch (Exception ex) { return new { success = false, message = ex.Message, stackTrace = ex.StackTrace }; } } private static object HandleParticleSystemAction(JObject @params, string action) { switch (action) { case "get_info": return ParticleRead.GetInfo(@params); case "set_main": return ParticleWrite.SetMain(@params); case "set_emission": return ParticleWrite.SetEmission(@params); case "set_shape": return ParticleWrite.SetShape(@params); case "set_color_over_lifetime": return ParticleWrite.SetColorOverLifetime(@params); case "set_size_over_lifetime": return ParticleWrite.SetSizeOverLifetime(@params); case "set_velocity_over_lifetime": return ParticleWrite.SetVelocityOverLifetime(@params); case "set_noise": return ParticleWrite.SetNoise(@params); case "set_renderer": return ParticleWrite.SetRenderer(@params); case "enable_module": return ParticleControl.EnableModule(@params); case "play": return ParticleControl.Control(@params, "play"); case "stop": return ParticleControl.Control(@params, "stop"); case "pause": return ParticleControl.Control(@params, "pause"); case "restart": return ParticleControl.Control(@params, "restart"); case "clear": return ParticleControl.Control(@params, "clear"); case "add_burst": return ParticleControl.AddBurst(@params); case "clear_bursts": return ParticleControl.ClearBursts(@params); default: return new { success = false, message = $"Unknown particle action: {action}. Valid: get_info, set_main, set_emission, set_shape, set_color_over_lifetime, set_size_over_lifetime, set_velocity_over_lifetime, set_noise, set_renderer, enable_module, play, stop, pause, restart, clear, add_burst, clear_bursts" }; } } // ==================== VFX GRAPH ==================== #region VFX Graph private static object HandleVFXGraphAction(JObject @params, string action) { #if !UNITY_VFX_GRAPH return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" }; #else switch (action) { // Asset management case "create_asset": return VFXCreateAsset(@params); case "assign_asset": return VFXAssignAsset(@params); case "list_templates": return VFXListTemplates(@params); case "list_assets": return VFXListAssets(@params); // Runtime parameter control case "get_info": return VFXGetInfo(@params); case "set_float": return VFXSetParameter(@params, (vfx, n, v) => vfx.SetFloat(n, v)); case "set_int": return VFXSetParameter(@params, (vfx, n, v) => vfx.SetInt(n, v)); case "set_bool": return VFXSetParameter(@params, (vfx, n, v) => vfx.SetBool(n, v)); case "set_vector2": return VFXSetVector(@params, 2); case "set_vector3": return VFXSetVector(@params, 3); case "set_vector4": return VFXSetVector(@params, 4); case "set_color": return VFXSetColor(@params); case "set_gradient": return VFXSetGradient(@params); case "set_texture": return VFXSetTexture(@params); case "set_mesh": return VFXSetMesh(@params); case "set_curve": return VFXSetCurve(@params); case "send_event": return VFXSendEvent(@params); case "play": return VFXControl(@params, "play"); case "stop": return VFXControl(@params, "stop"); case "pause": return VFXControl(@params, "pause"); case "reinit": return VFXControl(@params, "reinit"); case "set_playback_speed": return VFXSetPlaybackSpeed(@params); case "set_seed": return VFXSetSeed(@params); default: return new { success = false, message = $"Unknown vfx action: {action}. Valid: create_asset, assign_asset, list_templates, list_assets, get_info, set_float, set_int, set_bool, set_vector2/3/4, set_color, set_gradient, set_texture, set_mesh, set_curve, send_event, play, stop, pause, reinit, set_playback_speed, set_seed" }; } #endif } #if UNITY_VFX_GRAPH private static VisualEffect FindVisualEffect(JObject @params) { GameObject go = ManageVfxCommon.FindTargetGameObject(@params); return go?.GetComponent(); } /// /// Creates a new VFX Graph asset file from a template /// private static object VFXCreateAsset(JObject @params) { string assetName = @params["assetName"]?.ToString(); string folderPath = @params["folderPath"]?.ToString() ?? "Assets/VFX"; string template = @params["template"]?.ToString() ?? "empty"; if (string.IsNullOrEmpty(assetName)) return new { success = false, message = "assetName is required" }; // Ensure folder exists if (!AssetDatabase.IsValidFolder(folderPath)) { string[] folders = folderPath.Split('/'); string currentPath = folders[0]; for (int i = 1; i < folders.Length; i++) { string newPath = currentPath + "/" + folders[i]; if (!AssetDatabase.IsValidFolder(newPath)) { AssetDatabase.CreateFolder(currentPath, folders[i]); } currentPath = newPath; } } string assetPath = $"{folderPath}/{assetName}.vfx"; // Check if asset already exists if (AssetDatabase.LoadAssetAtPath(assetPath) != null) { bool overwrite = @params["overwrite"]?.ToObject() ?? false; if (!overwrite) return new { success = false, message = $"Asset already exists at {assetPath}. Set overwrite=true to replace." }; AssetDatabase.DeleteAsset(assetPath); } // Find and copy template string templatePath = FindVFXTemplate(template); UnityEngine.VFX.VisualEffectAsset newAsset = null; if (!string.IsNullOrEmpty(templatePath) && System.IO.File.Exists(templatePath)) { // templatePath is a full filesystem path, need to copy file directly // Get the full destination path string projectRoot = System.IO.Path.GetDirectoryName(Application.dataPath); string fullDestPath = System.IO.Path.Combine(projectRoot, assetPath); // Ensure directory exists string destDir = System.IO.Path.GetDirectoryName(fullDestPath); if (!System.IO.Directory.Exists(destDir)) System.IO.Directory.CreateDirectory(destDir); // Copy the file System.IO.File.Copy(templatePath, fullDestPath, true); AssetDatabase.Refresh(); newAsset = AssetDatabase.LoadAssetAtPath(assetPath); } else { // Create empty VFX asset using reflection to access internal API // Note: Develop in Progress, TODO:// Find authenticated way to create VFX asset try { // Try to use VisualEffectAssetEditorUtility.CreateNewAsset if available var utilityType = System.Type.GetType("UnityEditor.VFX.VisualEffectAssetEditorUtility, Unity.VisualEffectGraph.Editor"); if (utilityType != null) { var createMethod = utilityType.GetMethod("CreateNewAsset", BindingFlags.Public | BindingFlags.Static); if (createMethod != null) { createMethod.Invoke(null, new object[] { assetPath }); AssetDatabase.Refresh(); newAsset = AssetDatabase.LoadAssetAtPath(assetPath); } } // Fallback: Create a ScriptableObject-based asset if (newAsset == null) { // Try direct creation via internal constructor var resourceType = System.Type.GetType("UnityEditor.VFX.VisualEffectResource, Unity.VisualEffectGraph.Editor"); if (resourceType != null) { var createMethod = resourceType.GetMethod("CreateNewAsset", BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); if (createMethod != null) { var resource = createMethod.Invoke(null, new object[] { assetPath }); AssetDatabase.Refresh(); newAsset = AssetDatabase.LoadAssetAtPath(assetPath); } } } } catch (Exception ex) { return new { success = false, message = $"Failed to create VFX asset: {ex.Message}" }; } } if (newAsset == null) { return new { success = false, message = "Failed to create VFX asset. Try using a template from list_templates." }; } return new { success = true, message = $"Created VFX asset: {assetPath}", data = new { assetPath = assetPath, assetName = newAsset.name, template = template } }; } /// /// Finds VFX template path by name /// private static string FindVFXTemplate(string templateName) { // Get the actual filesystem path for the VFX Graph package using PackageManager API var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.visualeffectgraph"); var searchPaths = new List(); if (packageInfo != null) { // Use the resolved path from PackageManager (handles Library/PackageCache paths) searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Editor/Templates")); searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Samples")); } // Also search project-local paths searchPaths.Add("Assets/VFX/Templates"); string[] templatePatterns = new[] { $"{templateName}.vfx", $"VFX{templateName}.vfx", $"Simple{templateName}.vfx", $"{templateName}VFX.vfx" }; foreach (string basePath in searchPaths) { if (!System.IO.Directory.Exists(basePath)) continue; foreach (string pattern in templatePatterns) { string[] files = System.IO.Directory.GetFiles(basePath, pattern, System.IO.SearchOption.AllDirectories); if (files.Length > 0) return files[0]; } // Also search by partial match try { string[] allVfxFiles = System.IO.Directory.GetFiles(basePath, "*.vfx", System.IO.SearchOption.AllDirectories); foreach (string file in allVfxFiles) { if (System.IO.Path.GetFileNameWithoutExtension(file).ToLower().Contains(templateName.ToLower())) return file; } } catch { } } // Search in project assets string[] guids = AssetDatabase.FindAssets("t:VisualEffectAsset " + templateName); if (guids.Length > 0) { return AssetDatabase.GUIDToAssetPath(guids[0]); } return null; } /// /// Assigns a VFX asset to a VisualEffect component /// private static object VFXAssignAsset(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect component not found" }; string assetPath = @params["assetPath"]?.ToString(); if (string.IsNullOrEmpty(assetPath)) return new { success = false, message = "assetPath is required" }; // Normalize path if (!assetPath.StartsWith("Assets/") && !assetPath.StartsWith("Packages/")) assetPath = "Assets/" + assetPath; if (!assetPath.EndsWith(".vfx")) assetPath += ".vfx"; var asset = AssetDatabase.LoadAssetAtPath(assetPath); if (asset == null) { // Try searching by name string searchName = System.IO.Path.GetFileNameWithoutExtension(assetPath); string[] guids = AssetDatabase.FindAssets($"t:VisualEffectAsset {searchName}"); if (guids.Length > 0) { assetPath = AssetDatabase.GUIDToAssetPath(guids[0]); asset = AssetDatabase.LoadAssetAtPath(assetPath); } } if (asset == null) return new { success = false, message = $"VFX asset not found: {assetPath}" }; Undo.RecordObject(vfx, "Assign VFX Asset"); vfx.visualEffectAsset = asset; EditorUtility.SetDirty(vfx); return new { success = true, message = $"Assigned VFX asset '{asset.name}' to {vfx.gameObject.name}", data = new { gameObject = vfx.gameObject.name, assetName = asset.name, assetPath = assetPath } }; } /// /// Lists available VFX templates /// private static object VFXListTemplates(JObject @params) { var templates = new List(); // Get the actual filesystem path for the VFX Graph package using PackageManager API var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.visualeffectgraph"); var searchPaths = new List(); if (packageInfo != null) { // Use the resolved path from PackageManager (handles Library/PackageCache paths) searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Editor/Templates")); searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Samples")); } // Also search project-local paths searchPaths.Add("Assets/VFX/Templates"); searchPaths.Add("Assets/VFX"); // Precompute normalized package path for comparison string normalizedPackagePath = null; if (packageInfo != null) { normalizedPackagePath = packageInfo.resolvedPath.Replace("\\", "/"); } // Precompute the Assets base path for converting absolute paths to project-relative string assetsBasePath = Application.dataPath.Replace("\\", "/"); foreach (string basePath in searchPaths) { if (!System.IO.Directory.Exists(basePath)) continue; try { string[] vfxFiles = System.IO.Directory.GetFiles(basePath, "*.vfx", System.IO.SearchOption.AllDirectories); foreach (string file in vfxFiles) { string absolutePath = file.Replace("\\", "/"); string name = System.IO.Path.GetFileNameWithoutExtension(file); bool isPackage = normalizedPackagePath != null && absolutePath.StartsWith(normalizedPackagePath); // Convert absolute path to project-relative path string projectRelativePath; if (isPackage) { // For package paths, convert to Packages/... format projectRelativePath = "Packages/" + packageInfo.name + absolutePath.Substring(normalizedPackagePath.Length); } else if (absolutePath.StartsWith(assetsBasePath)) { // For project assets, convert to Assets/... format projectRelativePath = "Assets" + absolutePath.Substring(assetsBasePath.Length); } else { // Fallback: use the absolute path if we can't determine the relative path projectRelativePath = absolutePath; } templates.Add(new { name = name, path = projectRelativePath, source = isPackage ? "package" : "project" }); } } catch { } } // Also search project assets string[] guids = AssetDatabase.FindAssets("t:VisualEffectAsset"); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); if (!templates.Any(t => ((dynamic)t).path == path)) { string name = System.IO.Path.GetFileNameWithoutExtension(path); templates.Add(new { name = name, path = path, source = "project" }); } } return new { success = true, data = new { count = templates.Count, templates = templates } }; } /// /// Lists all VFX assets in the project /// private static object VFXListAssets(JObject @params) { string searchFolder = @params["folder"]?.ToString(); string searchPattern = @params["search"]?.ToString(); string filter = "t:VisualEffectAsset"; if (!string.IsNullOrEmpty(searchPattern)) filter += " " + searchPattern; string[] guids; if (!string.IsNullOrEmpty(searchFolder)) guids = AssetDatabase.FindAssets(filter, new[] { searchFolder }); else guids = AssetDatabase.FindAssets(filter); var assets = new List(); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); var asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) { assets.Add(new { name = asset.name, path = path, guid = guid }); } } return new { success = true, data = new { count = assets.Count, assets = assets } }; } private static object VFXGetInfo(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; return new { success = true, data = new { gameObject = vfx.gameObject.name, assetName = vfx.visualEffectAsset?.name ?? "None", aliveParticleCount = vfx.aliveParticleCount, culled = vfx.culled, pause = vfx.pause, playRate = vfx.playRate, startSeed = vfx.startSeed } }; } private static object VFXSetParameter(JObject @params, Action setter) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); if (string.IsNullOrEmpty(param)) return new { success = false, message = "Parameter name required" }; JToken valueToken = @params["value"]; if (valueToken == null) return new { success = false, message = "Value required" }; Undo.RecordObject(vfx, $"Set VFX {param}"); T value = valueToken.ToObject(); setter(vfx, param, value); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set {param} = {value}" }; } private static object VFXSetVector(JObject @params, int dims) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); if (string.IsNullOrEmpty(param)) return new { success = false, message = "Parameter name required" }; Vector4 vec = ManageVfxCommon.ParseVector4(@params["value"]); Undo.RecordObject(vfx, $"Set VFX {param}"); switch (dims) { case 2: vfx.SetVector2(param, new Vector2(vec.x, vec.y)); break; case 3: vfx.SetVector3(param, new Vector3(vec.x, vec.y, vec.z)); break; case 4: vfx.SetVector4(param, vec); break; } EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set {param}" }; } private static object VFXSetColor(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); if (string.IsNullOrEmpty(param)) return new { success = false, message = "Parameter name required" }; Color color = ManageVfxCommon.ParseColor(@params["value"]); Undo.RecordObject(vfx, $"Set VFX Color {param}"); vfx.SetVector4(param, new Vector4(color.r, color.g, color.b, color.a)); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set color {param}" }; } private static object VFXSetGradient(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); if (string.IsNullOrEmpty(param)) return new { success = false, message = "Parameter name required" }; Gradient gradient = ManageVfxCommon.ParseGradient(@params["gradient"]); Undo.RecordObject(vfx, $"Set VFX Gradient {param}"); vfx.SetGradient(param, gradient); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set gradient {param}" }; } private static object VFXSetTexture(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); string path = @params["texturePath"]?.ToString(); if (string.IsNullOrEmpty(param) || string.IsNullOrEmpty(path)) return new { success = false, message = "Parameter and texturePath required" }; var findInst = new JObject { ["find"] = path }; Texture tex = ObjectResolver.Resolve(findInst, typeof(Texture)) as Texture; if (tex == null) return new { success = false, message = $"Texture not found: {path}" }; Undo.RecordObject(vfx, $"Set VFX Texture {param}"); vfx.SetTexture(param, tex); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set texture {param} = {tex.name}" }; } private static object VFXSetMesh(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); string path = @params["meshPath"]?.ToString(); if (string.IsNullOrEmpty(param) || string.IsNullOrEmpty(path)) return new { success = false, message = "Parameter and meshPath required" }; var findInst = new JObject { ["find"] = path }; Mesh mesh = ObjectResolver.Resolve(findInst, typeof(Mesh)) as Mesh; if (mesh == null) return new { success = false, message = $"Mesh not found: {path}" }; Undo.RecordObject(vfx, $"Set VFX Mesh {param}"); vfx.SetMesh(param, mesh); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set mesh {param} = {mesh.name}" }; } private static object VFXSetCurve(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string param = @params["parameter"]?.ToString(); if (string.IsNullOrEmpty(param)) return new { success = false, message = "Parameter name required" }; AnimationCurve curve = ManageVfxCommon.ParseAnimationCurve(@params["curve"], 1f); Undo.RecordObject(vfx, $"Set VFX Curve {param}"); vfx.SetAnimationCurve(param, curve); EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set curve {param}" }; } private static object VFXSendEvent(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; string eventName = @params["eventName"]?.ToString(); if (string.IsNullOrEmpty(eventName)) return new { success = false, message = "Event name required" }; VFXEventAttribute attr = vfx.CreateVFXEventAttribute(); if (@params["position"] != null) attr.SetVector3("position", ManageVfxCommon.ParseVector3(@params["position"])); if (@params["velocity"] != null) attr.SetVector3("velocity", ManageVfxCommon.ParseVector3(@params["velocity"])); if (@params["color"] != null) { var c = ManageVfxCommon.ParseColor(@params["color"]); attr.SetVector3("color", new Vector3(c.r, c.g, c.b)); } if (@params["size"] != null) attr.SetFloat("size", @params["size"].ToObject()); if (@params["lifetime"] != null) attr.SetFloat("lifetime", @params["lifetime"].ToObject()); vfx.SendEvent(eventName, attr); return new { success = true, message = $"Sent event '{eventName}'" }; } private static object VFXControl(JObject @params, string action) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; switch (action) { case "play": vfx.Play(); break; case "stop": vfx.Stop(); break; case "pause": vfx.pause = !vfx.pause; break; case "reinit": vfx.Reinit(); break; } return new { success = true, message = $"VFX {action}", isPaused = vfx.pause }; } private static object VFXSetPlaybackSpeed(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; float rate = @params["playRate"]?.ToObject() ?? 1f; Undo.RecordObject(vfx, "Set VFX Play Rate"); vfx.playRate = rate; EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set play rate = {rate}" }; } private static object VFXSetSeed(JObject @params) { VisualEffect vfx = FindVisualEffect(@params); if (vfx == null) return new { success = false, message = "VisualEffect not found" }; uint seed = @params["seed"]?.ToObject() ?? 0; bool resetOnPlay = @params["resetSeedOnPlay"]?.ToObject() ?? true; Undo.RecordObject(vfx, "Set VFX Seed"); vfx.startSeed = seed; vfx.resetSeedOnPlay = resetOnPlay; EditorUtility.SetDirty(vfx); return new { success = true, message = $"Set seed = {seed}" }; } #endif #endregion private static object HandleLineRendererAction(JObject @params, string action) { switch (action) { case "get_info": return LineRead.GetInfo(@params); case "set_positions": return LineWrite.SetPositions(@params); case "add_position": return LineWrite.AddPosition(@params); case "set_position": return LineWrite.SetPosition(@params); case "set_width": return LineWrite.SetWidth(@params); case "set_color": return LineWrite.SetColor(@params); case "set_material": return LineWrite.SetMaterial(@params); case "set_properties": return LineWrite.SetProperties(@params); case "clear": return LineWrite.Clear(@params); case "create_line": return LineCreate.CreateLine(@params); case "create_circle": return LineCreate.CreateCircle(@params); case "create_arc": return LineCreate.CreateArc(@params); case "create_bezier": return LineCreate.CreateBezier(@params); default: return new { success = false, message = $"Unknown line action: {action}. Valid: get_info, set_positions, add_position, set_position, set_width, set_color, set_material, set_properties, clear, create_line, create_circle, create_arc, create_bezier" }; } } private static object HandleTrailRendererAction(JObject @params, string action) { switch (action) { case "get_info": return TrailRead.GetInfo(@params); case "set_time": return TrailWrite.SetTime(@params); case "set_width": return TrailWrite.SetWidth(@params); case "set_color": return TrailWrite.SetColor(@params); case "set_material": return TrailWrite.SetMaterial(@params); case "set_properties": return TrailWrite.SetProperties(@params); case "clear": return TrailControl.Clear(@params); case "emit": return TrailControl.Emit(@params); default: return new { success = false, message = $"Unknown trail action: {action}. Valid: get_info, set_time, set_width, set_color, set_material, set_properties, clear, emit" }; } } } }