Token Optimization for VFX (#626)
* Token Optimization for VFX * Update VFXs * Small fix based on AI feedback * Update ManageVFX.csmain
parent
2eb26b85cf
commit
bb56f78ad3
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MCPForUnity.Editor.Helpers
|
||||
{
|
||||
|
|
@ -14,6 +16,15 @@ namespace MCPForUnity.Editor.Helpers
|
|||
Custom
|
||||
}
|
||||
|
||||
internal enum VFXComponentType
|
||||
{
|
||||
ParticleSystem,
|
||||
LineRenderer,
|
||||
TrailRenderer
|
||||
}
|
||||
|
||||
private static Dictionary<string, Material> s_DefaultVFXMaterials = new Dictionary<string, Material>();
|
||||
|
||||
private static readonly string[] BuiltInLitShaders = { "Standard", "Legacy Shaders/Diffuse" };
|
||||
private static readonly string[] BuiltInUnlitShaders = { "Unlit/Color", "Unlit/Texture" };
|
||||
private static readonly string[] UrpLitShaders = { "Universal Render Pipeline/Lit", "Universal Render Pipeline/Simple Lit" };
|
||||
|
|
@ -192,5 +203,82 @@ namespace MCPForUnity.Editor.Helpers
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Material GetOrCreateDefaultVFXMaterial(VFXComponentType componentType)
|
||||
{
|
||||
var pipeline = GetActivePipeline();
|
||||
string cacheKey = $"{pipeline}_{componentType}";
|
||||
|
||||
if (s_DefaultVFXMaterials.TryGetValue(cacheKey, out Material cachedMaterial) && cachedMaterial != null)
|
||||
{
|
||||
return cachedMaterial;
|
||||
}
|
||||
|
||||
Material material = null;
|
||||
|
||||
if (pipeline == PipelineKind.BuiltIn)
|
||||
{
|
||||
string builtinPath = componentType == VFXComponentType.ParticleSystem
|
||||
? "Default-Particle.mat"
|
||||
: "Default-Line.mat";
|
||||
|
||||
material = AssetDatabase.GetBuiltinExtraResource<Material>(builtinPath);
|
||||
}
|
||||
|
||||
if (material == null)
|
||||
{
|
||||
Shader shader = ResolveDefaultUnlitShader(pipeline);
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
if (shader != null)
|
||||
{
|
||||
material = new Material(shader);
|
||||
material.name = $"Auto_Default_{componentType}_{pipeline}";
|
||||
|
||||
// Set default color (white is standard for VFX)
|
||||
if (material.HasProperty("_Color"))
|
||||
{
|
||||
material.SetColor("_Color", Color.white);
|
||||
}
|
||||
if (material.HasProperty("_BaseColor"))
|
||||
{
|
||||
material.SetColor("_BaseColor", Color.white);
|
||||
}
|
||||
|
||||
if (componentType == VFXComponentType.ParticleSystem)
|
||||
{
|
||||
material.renderQueue = 3000;
|
||||
if (material.HasProperty("_Mode"))
|
||||
{
|
||||
material.SetFloat("_Mode", 2);
|
||||
}
|
||||
if (material.HasProperty("_SrcBlend"))
|
||||
{
|
||||
material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
||||
}
|
||||
if (material.HasProperty("_DstBlend"))
|
||||
{
|
||||
material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
||||
}
|
||||
if (material.HasProperty("_ZWrite"))
|
||||
{
|
||||
material.SetFloat("_ZWrite", 0);
|
||||
}
|
||||
}
|
||||
|
||||
McpLog.Info($"[RenderPipelineUtility] Created default VFX material for {componentType} using {shader.name}");
|
||||
}
|
||||
}
|
||||
|
||||
if (material != null)
|
||||
{
|
||||
s_DefaultVFXMaterials[cacheKey] = material;
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,43 @@ namespace MCPForUnity.Editor.Helpers
|
|||
/// </summary>
|
||||
public static class RendererHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures a renderer has a material assigned. If not, auto-assigns a default material
|
||||
/// based on the render pipeline and component type.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The renderer to check</param>
|
||||
public static void EnsureMaterial(Renderer renderer)
|
||||
{
|
||||
if (renderer == null || renderer.sharedMaterial != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderPipelineUtility.VFXComponentType? componentType = null;
|
||||
if (renderer is ParticleSystemRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.ParticleSystem;
|
||||
}
|
||||
else if (renderer is LineRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.LineRenderer;
|
||||
}
|
||||
else if (renderer is TrailRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.TrailRenderer;
|
||||
}
|
||||
|
||||
if (componentType.HasValue)
|
||||
{
|
||||
Material defaultMat = RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(componentType.Value);
|
||||
if (defaultMat != null)
|
||||
{
|
||||
Undo.RecordObject(renderer, "Assign default VFX material");
|
||||
EditorUtility.SetDirty(renderer);
|
||||
renderer.sharedMaterial = defaultMat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies common Renderer properties (shadows, lighting, probes, sorting, rendering layer).
|
||||
|
|
@ -127,12 +164,48 @@ namespace MCPForUnity.Editor.Helpers
|
|||
/// <param name="params">JSON parameters containing materialPath</param>
|
||||
/// <param name="undoName">Name for the undo operation</param>
|
||||
/// <param name="findMaterial">Function to find material by path</param>
|
||||
public static object SetRendererMaterial(Renderer renderer, JObject @params, string undoName, Func<string, Material> findMaterial)
|
||||
/// <param name="autoAssignDefault">If true, auto-assigns default material when materialPath is not provided</param>
|
||||
public static object SetRendererMaterial(Renderer renderer, JObject @params, string undoName, Func<string, Material> findMaterial, bool autoAssignDefault = true)
|
||||
{
|
||||
if (renderer == null) return new { success = false, message = "Renderer not found" };
|
||||
|
||||
string path = @params["materialPath"]?.ToString();
|
||||
if (string.IsNullOrEmpty(path)) return new { success = false, message = "materialPath required" };
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (!autoAssignDefault)
|
||||
{
|
||||
return new { success = false, message = "materialPath required" };
|
||||
}
|
||||
|
||||
RenderPipelineUtility.VFXComponentType? componentType = null;
|
||||
if (renderer is ParticleSystemRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.ParticleSystem;
|
||||
}
|
||||
else if (renderer is LineRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.LineRenderer;
|
||||
}
|
||||
else if (renderer is TrailRenderer)
|
||||
{
|
||||
componentType = RenderPipelineUtility.VFXComponentType.TrailRenderer;
|
||||
}
|
||||
|
||||
if (componentType.HasValue)
|
||||
{
|
||||
Material defaultMat = RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(componentType.Value);
|
||||
if (defaultMat != null)
|
||||
{
|
||||
Undo.RecordObject(renderer, undoName);
|
||||
renderer.sharedMaterial = defaultMat;
|
||||
EditorUtility.SetDirty(renderer);
|
||||
return new { success = true, message = $"Auto-assigned default material: {defaultMat.name}" };
|
||||
}
|
||||
}
|
||||
|
||||
return new { success = false, message = "materialPath required" };
|
||||
}
|
||||
|
||||
Material mat = findMaterial(path);
|
||||
if (mat == null) return new { success = false, message = $"Material not found: {path}" };
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ namespace MCPForUnity.Editor.Tools
|
|||
}
|
||||
|
||||
// Extract parameters
|
||||
string action = @params["action"]?.ToString()?.ToLower();
|
||||
string action = @params["action"]?.ToString()?.ToLowerInvariant();
|
||||
string name = @params["name"]?.ToString();
|
||||
string path = @params["path"]?.ToString(); // Relative to Assets/
|
||||
string contents = null;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Vfx
|
||||
{
|
||||
|
|
@ -18,6 +19,29 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
lr.positionCount = 2;
|
||||
lr.SetPosition(0, start);
|
||||
lr.SetPosition(1, end);
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
// Apply optional width
|
||||
if (@params["width"] != null)
|
||||
{
|
||||
float w = @params["width"].ToObject<float>();
|
||||
lr.startWidth = w;
|
||||
lr.endWidth = w;
|
||||
}
|
||||
if (@params["startWidth"] != null) lr.startWidth = @params["startWidth"].ToObject<float>();
|
||||
if (@params["endWidth"] != null) lr.endWidth = @params["endWidth"].ToObject<float>();
|
||||
|
||||
// Apply optional color
|
||||
if (@params["color"] != null)
|
||||
{
|
||||
Color c = ManageVfxCommon.ParseColor(@params["color"]);
|
||||
lr.startColor = c;
|
||||
lr.endColor = c;
|
||||
}
|
||||
if (@params["startColor"] != null) lr.startColor = ManageVfxCommon.ParseColor(@params["startColor"]);
|
||||
if (@params["endColor"] != null) lr.endColor = ManageVfxCommon.ParseColor(@params["endColor"]);
|
||||
|
||||
EditorUtility.SetDirty(lr);
|
||||
|
||||
return new { success = true, message = "Created line" };
|
||||
|
|
@ -49,6 +73,28 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
lr.SetPosition(i, point);
|
||||
}
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
// Apply optional width
|
||||
if (@params["width"] != null)
|
||||
{
|
||||
float w = @params["width"].ToObject<float>();
|
||||
lr.startWidth = w;
|
||||
lr.endWidth = w;
|
||||
}
|
||||
if (@params["startWidth"] != null) lr.startWidth = @params["startWidth"].ToObject<float>();
|
||||
if (@params["endWidth"] != null) lr.endWidth = @params["endWidth"].ToObject<float>();
|
||||
|
||||
// Apply optional color
|
||||
if (@params["color"] != null)
|
||||
{
|
||||
Color c = ManageVfxCommon.ParseColor(@params["color"]);
|
||||
lr.startColor = c;
|
||||
lr.endColor = c;
|
||||
}
|
||||
if (@params["startColor"] != null) lr.startColor = ManageVfxCommon.ParseColor(@params["startColor"]);
|
||||
if (@params["endColor"] != null) lr.endColor = ManageVfxCommon.ParseColor(@params["endColor"]);
|
||||
|
||||
EditorUtility.SetDirty(lr);
|
||||
return new { success = true, message = $"Created circle with {segments} segments" };
|
||||
}
|
||||
|
|
@ -82,6 +128,28 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
lr.SetPosition(i, point);
|
||||
}
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
// Apply optional width
|
||||
if (@params["width"] != null)
|
||||
{
|
||||
float w = @params["width"].ToObject<float>();
|
||||
lr.startWidth = w;
|
||||
lr.endWidth = w;
|
||||
}
|
||||
if (@params["startWidth"] != null) lr.startWidth = @params["startWidth"].ToObject<float>();
|
||||
if (@params["endWidth"] != null) lr.endWidth = @params["endWidth"].ToObject<float>();
|
||||
|
||||
// Apply optional color
|
||||
if (@params["color"] != null)
|
||||
{
|
||||
Color c = ManageVfxCommon.ParseColor(@params["color"]);
|
||||
lr.startColor = c;
|
||||
lr.endColor = c;
|
||||
}
|
||||
if (@params["startColor"] != null) lr.startColor = ManageVfxCommon.ParseColor(@params["startColor"]);
|
||||
if (@params["endColor"] != null) lr.endColor = ManageVfxCommon.ParseColor(@params["endColor"]);
|
||||
|
||||
EditorUtility.SetDirty(lr);
|
||||
return new { success = true, message = $"Created arc with {segments} segments" };
|
||||
}
|
||||
|
|
@ -123,6 +191,28 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
lr.SetPosition(i, point);
|
||||
}
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
// Apply optional width
|
||||
if (@params["width"] != null)
|
||||
{
|
||||
float w = @params["width"].ToObject<float>();
|
||||
lr.startWidth = w;
|
||||
lr.endWidth = w;
|
||||
}
|
||||
if (@params["startWidth"] != null) lr.startWidth = @params["startWidth"].ToObject<float>();
|
||||
if (@params["endWidth"] != null) lr.endWidth = @params["endWidth"].ToObject<float>();
|
||||
|
||||
// Apply optional color
|
||||
if (@params["color"] != null)
|
||||
{
|
||||
Color c = ManageVfxCommon.ParseColor(@params["color"]);
|
||||
lr.startColor = c;
|
||||
lr.endColor = c;
|
||||
}
|
||||
if (@params["startColor"] != null) lr.startColor = ManageVfxCommon.ParseColor(@params["startColor"]);
|
||||
if (@params["endColor"] != null) lr.endColor = ManageVfxCommon.ParseColor(@params["endColor"]);
|
||||
|
||||
EditorUtility.SetDirty(lr);
|
||||
return new { success = true, message = $"Created {(isQuadratic ? "quadratic" : "cubic")} Bezier" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
JArray posArr = @params["positions"] as JArray;
|
||||
if (posArr == null) return new { success = false, message = "Positions array required" };
|
||||
|
||||
|
|
@ -35,6 +37,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
Vector3 pos = ManageVfxCommon.ParseVector3(@params["position"]);
|
||||
|
||||
Undo.RecordObject(lr, "Add Line Position");
|
||||
|
|
@ -51,6 +55,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
int index = @params["index"]?.ToObject<int>() ?? -1;
|
||||
if (index < 0 || index >= lr.positionCount) return new { success = false, message = $"Invalid index {index}" };
|
||||
|
||||
|
|
@ -68,6 +74,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
Undo.RecordObject(lr, "Set Line Width");
|
||||
var changes = new List<string>();
|
||||
|
||||
|
|
@ -85,6 +93,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
Undo.RecordObject(lr, "Set Line Color");
|
||||
var changes = new List<string>();
|
||||
|
||||
|
|
@ -108,9 +118,49 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
LineRenderer lr = LineRead.FindLineRenderer(@params);
|
||||
if (lr == null) return new { success = false, message = "LineRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(lr);
|
||||
|
||||
Undo.RecordObject(lr, "Set Line Properties");
|
||||
var changes = new List<string>();
|
||||
|
||||
// Handle material if provided
|
||||
if (@params["materialPath"] != null)
|
||||
{
|
||||
Material mat = ManageVfxCommon.FindMaterialByPath(@params["materialPath"].ToString());
|
||||
if (mat != null)
|
||||
{
|
||||
lr.sharedMaterial = mat;
|
||||
changes.Add($"material={mat.name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn($"Material not found: {@params["materialPath"]}");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle positions if provided
|
||||
if (@params["positions"] != null)
|
||||
{
|
||||
JArray posArr = @params["positions"] as JArray;
|
||||
if (posArr != null && posArr.Count > 0)
|
||||
{
|
||||
var positions = new Vector3[posArr.Count];
|
||||
for (int i = 0; i < posArr.Count; i++)
|
||||
{
|
||||
positions[i] = ManageVfxCommon.ParseVector3(posArr[i]);
|
||||
}
|
||||
lr.positionCount = positions.Length;
|
||||
lr.SetPositions(positions);
|
||||
changes.Add($"positions({positions.Length})");
|
||||
}
|
||||
}
|
||||
else if (@params["positionCount"] != null)
|
||||
{
|
||||
int count = @params["positionCount"].ToObject<int>();
|
||||
lr.positionCount = count;
|
||||
changes.Add("positionCount");
|
||||
}
|
||||
|
||||
RendererHelpers.ApplyLineTrailProperties(@params, changes,
|
||||
v => lr.loop = v, v => lr.useWorldSpace = v,
|
||||
v => lr.numCornerVertices = v, v => lr.numCapVertices = v,
|
||||
|
|
|
|||
|
|
@ -20,14 +20,257 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
/// - Visual Effect Graph (modern GPU particles, currently only support HDRP, other SRPs may not work)
|
||||
/// - LineRenderer (lines, bezier curves, shapes)
|
||||
/// - TrailRenderer (motion trails)
|
||||
/// - More to come based on demand and feedback!
|
||||
///
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[McpForUnityTool("manage_vfx", AutoRegister = false)]
|
||||
public static class ManageVFX
|
||||
{
|
||||
private static readonly Dictionary<string, string> ParamAliases = new Dictionary<string, string>(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)
|
||||
{
|
||||
string action = @params["action"]?.ToString();
|
||||
JObject normalizedParams = NormalizeParams(@params);
|
||||
string action = normalizedParams["action"]?.ToString();
|
||||
if (string.IsNullOrEmpty(action))
|
||||
{
|
||||
return new { success = false, message = "Action is required" };
|
||||
|
|
@ -46,25 +289,25 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
// ParticleSystem actions (particle_*)
|
||||
if (actionLower.StartsWith("particle_"))
|
||||
{
|
||||
return HandleParticleSystemAction(@params, actionLower.Substring(9));
|
||||
return HandleParticleSystemAction(normalizedParams, actionLower.Substring(9));
|
||||
}
|
||||
|
||||
// VFX Graph actions (vfx_*)
|
||||
if (actionLower.StartsWith("vfx_"))
|
||||
{
|
||||
return HandleVFXGraphAction(@params, actionLower.Substring(4));
|
||||
return HandleVFXGraphAction(normalizedParams, actionLower.Substring(4));
|
||||
}
|
||||
|
||||
// LineRenderer actions (line_*)
|
||||
if (actionLower.StartsWith("line_"))
|
||||
{
|
||||
return HandleLineRendererAction(@params, actionLower.Substring(5));
|
||||
return HandleLineRendererAction(normalizedParams, actionLower.Substring(5));
|
||||
}
|
||||
|
||||
// TrailRenderer actions (trail_*)
|
||||
if (actionLower.StartsWith("trail_"))
|
||||
{
|
||||
return HandleTrailRendererAction(@params, actionLower.Substring(6));
|
||||
return HandleTrailRendererAction(normalizedParams, actionLower.Substring(6));
|
||||
}
|
||||
|
||||
return new { success = false, message = $"Unknown action: {action}. Actions must be prefixed with: particle_, vfx_, line_, or trail_" };
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Vfx
|
||||
{
|
||||
|
|
@ -42,6 +43,16 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned before playing
|
||||
if (action == "play" || action == "restart")
|
||||
{
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
bool withChildren = @params["withChildren"]?.ToObject<bool>() ?? true;
|
||||
|
||||
switch (action)
|
||||
|
|
@ -62,6 +73,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Add Burst");
|
||||
var emission = ps.emission;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,21 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned before any configuration
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
// Stop particle system if it's playing and duration needs to be changed
|
||||
bool wasPlaying = ps.isPlaying;
|
||||
bool needsStop = @params["duration"] != null && wasPlaying;
|
||||
if (needsStop)
|
||||
{
|
||||
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Main");
|
||||
var main = ps.main;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -34,6 +49,14 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
if (@params["maxParticles"] != null) { main.maxParticles = @params["maxParticles"].ToObject<int>(); changes.Add("maxParticles"); }
|
||||
|
||||
EditorUtility.SetDirty(ps);
|
||||
|
||||
// Restart particle system if it was playing
|
||||
if (needsStop && wasPlaying)
|
||||
{
|
||||
ps.Play(true);
|
||||
changes.Add("(restarted after duration change)");
|
||||
}
|
||||
|
||||
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +65,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Emission");
|
||||
var emission = ps.emission;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -59,6 +89,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Shape");
|
||||
var shape = ps.shape;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -82,6 +119,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Color Over Lifetime");
|
||||
var col = ps.colorOverLifetime;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -98,6 +142,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Size Over Lifetime");
|
||||
var sol = ps.sizeOverLifetime;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -130,6 +181,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Velocity Over Lifetime");
|
||||
var vol = ps.velocityOverLifetime;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -150,6 +208,13 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
|
||||
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
|
||||
|
||||
// Ensure material is assigned
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
}
|
||||
|
||||
Undo.RecordObject(ps, "Set ParticleSystem Noise");
|
||||
var noise = ps.noise;
|
||||
var changes = new List<string>();
|
||||
|
|
@ -174,6 +239,9 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer == null) return new { success = false, message = "ParticleSystemRenderer not found" };
|
||||
|
||||
// Ensure material is set before any other operations
|
||||
RendererHelpers.EnsureMaterial(renderer);
|
||||
|
||||
Undo.RecordObject(renderer, "Set ParticleSystem Renderer");
|
||||
var changes = new List<string>();
|
||||
|
||||
|
|
@ -199,10 +267,20 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
|
||||
if (@params["materialPath"] != null)
|
||||
{
|
||||
var findInst = new JObject { ["find"] = @params["materialPath"].ToString() };
|
||||
string matPath = @params["materialPath"].ToString();
|
||||
var findInst = new JObject { ["find"] = matPath };
|
||||
Material mat = ObjectResolver.Resolve(findInst, typeof(Material)) as Material;
|
||||
if (mat != null) { renderer.sharedMaterial = mat; changes.Add("material"); }
|
||||
if (mat != null)
|
||||
{
|
||||
renderer.sharedMaterial = mat;
|
||||
changes.Add($"material={mat.name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn($"Material not found at path: {matPath}. Keeping existing material.");
|
||||
}
|
||||
}
|
||||
|
||||
if (@params["trailMaterialPath"] != null)
|
||||
{
|
||||
var findInst = new JObject { ["find"] = @params["trailMaterialPath"].ToString() };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Vfx
|
||||
{
|
||||
|
|
@ -21,6 +22,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
|
||||
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(tr);
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
Vector3 pos = ManageVfxCommon.ParseVector3(@params["position"]);
|
||||
tr.AddPosition(pos);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
|
||||
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(tr);
|
||||
|
||||
float time = @params["time"]?.ToObject<float>() ?? 5f;
|
||||
|
||||
Undo.RecordObject(tr, "Set Trail Time");
|
||||
|
|
@ -28,6 +30,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
|
||||
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(tr);
|
||||
|
||||
Undo.RecordObject(tr, "Set Trail Width");
|
||||
var changes = new List<string>();
|
||||
|
||||
|
|
@ -45,6 +49,8 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
|
||||
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(tr);
|
||||
|
||||
Undo.RecordObject(tr, "Set Trail Color");
|
||||
var changes = new List<string>();
|
||||
|
||||
|
|
@ -68,9 +74,43 @@ namespace MCPForUnity.Editor.Tools.Vfx
|
|||
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
|
||||
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
|
||||
|
||||
RendererHelpers.EnsureMaterial(tr);
|
||||
|
||||
Undo.RecordObject(tr, "Set Trail Properties");
|
||||
var changes = new List<string>();
|
||||
|
||||
// Handle material if provided
|
||||
if (@params["materialPath"] != null)
|
||||
{
|
||||
Material mat = ManageVfxCommon.FindMaterialByPath(@params["materialPath"].ToString());
|
||||
if (mat != null)
|
||||
{
|
||||
tr.sharedMaterial = mat;
|
||||
changes.Add($"material={mat.name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn($"Material not found: {@params["materialPath"]}");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle time if provided
|
||||
if (@params["time"] != null) { tr.time = @params["time"].ToObject<float>(); changes.Add("time"); }
|
||||
|
||||
// Handle width properties if provided
|
||||
if (@params["width"] != null || @params["startWidth"] != null || @params["endWidth"] != null)
|
||||
{
|
||||
if (@params["width"] != null)
|
||||
{
|
||||
float w = @params["width"].ToObject<float>();
|
||||
tr.startWidth = w;
|
||||
tr.endWidth = w;
|
||||
changes.Add("width");
|
||||
}
|
||||
if (@params["startWidth"] != null) { tr.startWidth = @params["startWidth"].ToObject<float>(); changes.Add("startWidth"); }
|
||||
if (@params["endWidth"] != null) { tr.endWidth = @params["endWidth"].ToObject<float>(); changes.Add("endWidth"); }
|
||||
}
|
||||
|
||||
if (@params["minVertexDistance"] != null) { tr.minVertexDistance = @params["minVertexDistance"].ToObject<float>(); changes.Add("minVertexDistance"); }
|
||||
if (@params["autodestruct"] != null) { tr.autodestruct = @params["autodestruct"].ToObject<bool>(); changes.Add("autodestruct"); }
|
||||
if (@params["emitting"] != null) { tr.emitting = @params["emitting"].ToObject<bool>(); changes.Add("emitting"); }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,27 @@ from cli.utils.output import format_output, print_error, print_success
|
|||
from cli.utils.connection import run_command, UnityConnectionError
|
||||
|
||||
|
||||
_VFX_TOP_LEVEL_KEYS = {"action", "target", "searchMethod", "properties"}
|
||||
|
||||
|
||||
def _normalize_vfx_params(params: dict[str, Any]) -> dict[str, Any]:
|
||||
params = dict(params)
|
||||
properties: dict[str, Any] = {}
|
||||
for key in list(params.keys()):
|
||||
if key in _VFX_TOP_LEVEL_KEYS:
|
||||
continue
|
||||
properties[key] = params.pop(key)
|
||||
|
||||
if properties:
|
||||
existing = params.get("properties")
|
||||
if isinstance(existing, dict):
|
||||
params["properties"] = {**properties, **existing}
|
||||
else:
|
||||
params["properties"] = properties
|
||||
|
||||
return {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
|
||||
@click.group()
|
||||
def vfx():
|
||||
"""VFX operations - particle systems, line renderers, trails."""
|
||||
|
|
@ -43,7 +64,7 @@ def particle_info(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -70,7 +91,7 @@ def particle_play(target: str, with_children: bool, search_method: Optional[str]
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
if result.get("success"):
|
||||
print_success(f"Playing particle system: {target}")
|
||||
|
|
@ -93,7 +114,7 @@ def particle_stop(target: str, with_children: bool, search_method: Optional[str]
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
if result.get("success"):
|
||||
print_success(f"Stopped particle system: {target}")
|
||||
|
|
@ -113,7 +134,7 @@ def particle_pause(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -134,7 +155,7 @@ def particle_restart(target: str, with_children: bool, search_method: Optional[s
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -155,7 +176,7 @@ def particle_clear(target: str, with_children: bool, search_method: Optional[str
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -188,7 +209,7 @@ def line_info(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -223,7 +244,7 @@ def line_set_positions(target: str, positions: str, search_method: Optional[str]
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -253,7 +274,7 @@ def line_create_line(target: str, start: Tuple[float, float, float], end: Tuple[
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -286,7 +307,7 @@ def line_create_circle(target: str, center: Tuple[float, float, float], radius:
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -304,7 +325,7 @@ def line_clear(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -332,7 +353,7 @@ def trail_info(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -360,7 +381,7 @@ def trail_set_time(target: str, duration: float, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -378,7 +399,7 @@ def trail_clear(target: str, search_method: Optional[str]):
|
|||
params["searchMethod"] = search_method
|
||||
|
||||
try:
|
||||
result = run_command("manage_vfx", params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
@ -432,7 +453,7 @@ def vfx_raw(action: str, target: Optional[str], params: str, search_method: Opti
|
|||
# Merge extra params
|
||||
request_params.update(extra_params)
|
||||
try:
|
||||
result = run_command("manage_vfx", request_params, config)
|
||||
result = run_command("manage_vfx", _normalize_vfx_params(request_params), config)
|
||||
click.echo(format_output(result, config.format))
|
||||
except UnityConnectionError as e:
|
||||
print_error(str(e))
|
||||
|
|
|
|||
|
|
@ -454,7 +454,6 @@ def _normalize_import_settings(value: Any) -> tuple[dict | None, str | None]:
|
|||
description=(
|
||||
"Procedural texture generation for Unity. Creates textures with solid fills, "
|
||||
"patterns (checkerboard, stripes, dots, grid, brick), gradients, and noise. "
|
||||
"Supports full CRUD operations and one-call sprite creation.\n\n"
|
||||
"Actions: create, modify, delete, create_sprite, apply_pattern, apply_gradient, apply_noise"
|
||||
),
|
||||
annotations=ToolAnnotations(
|
||||
|
|
|
|||
|
|
@ -39,86 +39,15 @@ TRAIL_ACTIONS = [
|
|||
"trail_set_material", "trail_set_properties", "trail_clear", "trail_emit"
|
||||
]
|
||||
|
||||
ALL_ACTIONS = ["ping"] + PARTICLE_ACTIONS + \
|
||||
VFX_ACTIONS + LINE_ACTIONS + TRAIL_ACTIONS
|
||||
ALL_ACTIONS = ["ping"] + PARTICLE_ACTIONS + VFX_ACTIONS + LINE_ACTIONS + TRAIL_ACTIONS
|
||||
|
||||
|
||||
@mcp_for_unity_tool(
|
||||
description="""Unified VFX management for Unity visual effects components.
|
||||
|
||||
Each action prefix requires a specific component on the target GameObject:
|
||||
- `particle_*` actions require **ParticleSystem** component
|
||||
- `vfx_*` actions require **VisualEffect** component (+ com.unity.visualeffectgraph package)
|
||||
- `line_*` actions require **LineRenderer** component
|
||||
- `trail_*` actions require **TrailRenderer** component
|
||||
|
||||
**If the component doesn't exist, the action will FAIL
|
||||
Before using this tool, either:
|
||||
1. Use `manage_gameobject` with `action="get_components"` to check if component exists
|
||||
2. Use `manage_gameobject` with `action="add_component", component_name="ParticleSystem"` (or LineRenderer/TrailRenderer/VisualEffect) to add the component first
|
||||
3. Assign material to the component beforehand to avoid empty effects
|
||||
|
||||
**TARGETING:**
|
||||
Use `target` parameter to specify the GameObject:
|
||||
- By name: `target="Fire"` (finds first GameObject named "Fire")
|
||||
- By path: `target="Effects/Fire"` with `search_method="by_path"`
|
||||
- By instance ID: `target="12345"` with `search_method="by_id"` (most reliable)
|
||||
- By tag: `target="Player"` with `search_method="by_tag"`
|
||||
|
||||
**Component Types & Action Prefixes:**
|
||||
- `particle_*` - ParticleSystem (legacy particle effects)
|
||||
- `vfx_*` - Visual Effect Graph (modern GPU particles, requires com.unity.visualeffectgraph)
|
||||
- `line_*` - LineRenderer (lines, curves, shapes)
|
||||
- `trail_*` - TrailRenderer (motion trails)
|
||||
|
||||
**ParticleSystem Actions (particle_*):**
|
||||
- particle_get_info: Get particle system info
|
||||
- particle_set_main: Set main module (duration, looping, startLifetime, startSpeed, startSize, startColor, gravityModifier, maxParticles)
|
||||
- particle_set_emission: Set emission (rateOverTime, rateOverDistance)
|
||||
- particle_set_shape: Set shape (shapeType, radius, angle, arc, position, rotation, scale)
|
||||
- particle_set_color_over_lifetime, particle_set_size_over_lifetime, particle_set_velocity_over_lifetime
|
||||
- particle_set_noise: Set noise (strength, frequency, scrollSpeed)
|
||||
- particle_set_renderer: Set renderer (renderMode, material)
|
||||
- particle_enable_module: Enable/disable modules
|
||||
- particle_play/stop/pause/restart/clear: Playback control
|
||||
- particle_add_burst, particle_clear_bursts: Burst management
|
||||
|
||||
**VFX Graph Actions (vfx_*):**
|
||||
- **Asset Management:**
|
||||
- vfx_create_asset: Create a new VFX Graph asset file (requires: assetName, optional: folderPath, template, overwrite)
|
||||
- vfx_assign_asset: Assign a VFX asset to a VisualEffect component (requires: target, assetPath)
|
||||
- vfx_list_templates: List available VFX templates in project and packages
|
||||
- vfx_list_assets: List all VFX assets in project (optional: folder, search)
|
||||
- **Runtime Control:**
|
||||
- vfx_get_info: Get VFX info
|
||||
- vfx_set_float/int/bool: Set exposed parameters
|
||||
- vfx_set_vector2/vector3/vector4: Set vector parameters
|
||||
- vfx_set_color, vfx_set_gradient: Set color/gradient parameters
|
||||
- vfx_set_texture, vfx_set_mesh: Set asset parameters
|
||||
- vfx_set_curve: Set animation curve
|
||||
- vfx_send_event: Send events with attributes (position, velocity, color, size, lifetime)
|
||||
- vfx_play/stop/pause/reinit: Playback control
|
||||
- vfx_set_playback_speed, vfx_set_seed
|
||||
|
||||
**LineRenderer Actions (line_*):**
|
||||
- line_get_info: Get line info
|
||||
- line_set_positions: Set all positions
|
||||
- line_add_position, line_set_position: Modify positions
|
||||
- line_set_width: Set width (uniform, start/end, curve)
|
||||
- line_set_color: Set color (uniform, gradient)
|
||||
- line_set_material, line_set_properties
|
||||
- line_clear: Clear positions
|
||||
- line_create_line: Create simple line
|
||||
- line_create_circle: Create circle
|
||||
- line_create_arc: Create arc
|
||||
- line_create_bezier: Create Bezier curve
|
||||
|
||||
**TrailRenderer Actions (trail_*):**
|
||||
- trail_get_info: Get trail info
|
||||
- trail_set_time: Set trail duration
|
||||
- trail_set_width, trail_set_color, trail_set_material, trail_set_properties
|
||||
- trail_clear: Clear trail
|
||||
- trail_emit: Emit point (Unity 2021.1+)""",
|
||||
description=(
|
||||
"Manage Unity VFX components (ParticleSystem, VisualEffect, LineRenderer, TrailRenderer). "
|
||||
"Action prefixes: particle_*, vfx_*, line_*, trail_*. "
|
||||
"Action-specific parameters go in `properties` (keys match ManageVFX.cs)."
|
||||
),
|
||||
annotations=ToolAnnotations(
|
||||
title="Manage VFX",
|
||||
destructiveHint=True,
|
||||
|
|
@ -126,283 +55,16 @@ Use `target` parameter to specify the GameObject:
|
|||
)
|
||||
async def manage_vfx(
|
||||
ctx: Context,
|
||||
action: Annotated[str, "Action to perform. Use prefix: particle_, vfx_, line_, or trail_"],
|
||||
|
||||
# Target specification (common) - REQUIRED for most actions
|
||||
# Using str | None to accept any string format
|
||||
target: Annotated[str | None,
|
||||
"Target GameObject with the VFX component. Use name (e.g. 'Fire'), path ('Effects/Fire'), instance ID, or tag. The GameObject MUST have the required component (ParticleSystem/VisualEffect/LineRenderer/TrailRenderer) for the action prefix."] = None,
|
||||
action: Annotated[str, "Action to perform (prefix: particle_, vfx_, line_, trail_)."],
|
||||
target: Annotated[str | None, "Target GameObject (name/path/id)."] = None,
|
||||
search_method: Annotated[
|
||||
Literal["by_id", "by_name", "by_path", "by_tag", "by_layer"] | None,
|
||||
"How to find target: by_name (default), by_path (hierarchy path), by_id (instance ID - most reliable), by_tag, by_layer"
|
||||
"How to find the target GameObject.",
|
||||
] = None,
|
||||
properties: Annotated[
|
||||
dict[str, Any] | str | None,
|
||||
"Action-specific parameters (dict or JSON string).",
|
||||
] = None,
|
||||
|
||||
# === PARTICLE SYSTEM PARAMETERS ===
|
||||
# Main module - All use Any to accept string coercion from MCP clients
|
||||
duration: Annotated[Any,
|
||||
"[Particle] Duration in seconds (number or string)"] = None,
|
||||
looping: Annotated[Any,
|
||||
"[Particle] Whether to loop (bool or string 'true'/'false')"] = None,
|
||||
prewarm: Annotated[Any,
|
||||
"[Particle] Prewarm the system (bool or string)"] = None,
|
||||
start_delay: Annotated[Any,
|
||||
"[Particle] Start delay (number or MinMaxCurve dict)"] = None,
|
||||
start_lifetime: Annotated[Any,
|
||||
"[Particle] Particle lifetime (number or MinMaxCurve dict)"] = None,
|
||||
start_speed: Annotated[Any,
|
||||
"[Particle] Initial speed (number or MinMaxCurve dict)"] = None,
|
||||
start_size: Annotated[Any,
|
||||
"[Particle] Initial size (number or MinMaxCurve dict)"] = None,
|
||||
start_rotation: Annotated[Any,
|
||||
"[Particle] Initial rotation (number or MinMaxCurve dict)"] = None,
|
||||
start_color: Annotated[Any,
|
||||
"[Particle/VFX] Start color [r,g,b,a] (array, dict, or JSON string)"] = None,
|
||||
gravity_modifier: Annotated[Any,
|
||||
"[Particle] Gravity multiplier (number or MinMaxCurve dict)"] = None,
|
||||
simulation_space: Annotated[Literal["Local", "World",
|
||||
"Custom"] | None, "[Particle] Simulation space"] = None,
|
||||
scaling_mode: Annotated[Literal["Hierarchy", "Local",
|
||||
"Shape"] | None, "[Particle] Scaling mode"] = None,
|
||||
play_on_awake: Annotated[Any,
|
||||
"[Particle] Play on awake (bool or string)"] = None,
|
||||
max_particles: Annotated[Any,
|
||||
"[Particle] Maximum particles (integer or string)"] = None,
|
||||
|
||||
# Emission
|
||||
rate_over_time: Annotated[Any,
|
||||
"[Particle] Emission rate over time (number or MinMaxCurve dict)"] = None,
|
||||
rate_over_distance: Annotated[Any,
|
||||
"[Particle] Emission rate over distance (number or MinMaxCurve dict)"] = None,
|
||||
|
||||
# Shape
|
||||
shape_type: Annotated[Literal["Sphere", "Hemisphere", "Cone", "Box",
|
||||
"Circle", "Edge", "Donut"] | None, "[Particle] Shape type"] = None,
|
||||
radius: Annotated[Any,
|
||||
"[Particle/Line] Shape radius (number or string)"] = None,
|
||||
radius_thickness: Annotated[Any,
|
||||
"[Particle] Radius thickness 0-1 (number or string)"] = None,
|
||||
angle: Annotated[Any, "[Particle] Cone angle (number or string)"] = None,
|
||||
arc: Annotated[Any, "[Particle] Arc angle (number or string)"] = None,
|
||||
|
||||
# Noise
|
||||
strength: Annotated[Any,
|
||||
"[Particle] Noise strength (number or MinMaxCurve dict)"] = None,
|
||||
frequency: Annotated[Any,
|
||||
"[Particle] Noise frequency (number or string)"] = None,
|
||||
scroll_speed: Annotated[Any,
|
||||
"[Particle] Noise scroll speed (number or MinMaxCurve dict)"] = None,
|
||||
damping: Annotated[Any,
|
||||
"[Particle] Noise damping (bool or string)"] = None,
|
||||
octave_count: Annotated[Any,
|
||||
"[Particle] Noise octaves 1-4 (integer or string)"] = None,
|
||||
quality: Annotated[Literal["Low", "Medium", "High"]
|
||||
| None, "[Particle] Noise quality"] = None,
|
||||
|
||||
# Module control
|
||||
module: Annotated[str | None,
|
||||
"[Particle] Module name to enable/disable"] = None,
|
||||
enabled: Annotated[Any,
|
||||
"[Particle] Enable/disable module (bool or string)"] = None,
|
||||
|
||||
# Burst
|
||||
time: Annotated[Any,
|
||||
"[Particle/Trail] Burst time or trail duration (number or string)"] = None,
|
||||
count: Annotated[Any, "[Particle] Burst count (integer or string)"] = None,
|
||||
min_count: Annotated[Any,
|
||||
"[Particle] Min burst count (integer or string)"] = None,
|
||||
max_count: Annotated[Any,
|
||||
"[Particle] Max burst count (integer or string)"] = None,
|
||||
cycles: Annotated[Any,
|
||||
"[Particle] Burst cycles (integer or string)"] = None,
|
||||
interval: Annotated[Any,
|
||||
"[Particle] Burst interval (number or string)"] = None,
|
||||
probability: Annotated[Any,
|
||||
"[Particle] Burst probability 0-1 (number or string)"] = None,
|
||||
|
||||
# Playback
|
||||
with_children: Annotated[Any,
|
||||
"[Particle] Apply to children (bool or string)"] = None,
|
||||
|
||||
# === VFX GRAPH PARAMETERS ===
|
||||
# Asset management
|
||||
asset_name: Annotated[str | None,
|
||||
"[VFX] Name for new VFX asset (without .vfx extension)"] = None,
|
||||
folder_path: Annotated[str | None,
|
||||
"[VFX] Folder path for new asset (default: Assets/VFX)"] = None,
|
||||
template: Annotated[str | None,
|
||||
"[VFX] Template name for new asset (use vfx_list_templates to see available)"] = None,
|
||||
asset_path: Annotated[str | None,
|
||||
"[VFX] Path to VFX asset to assign (e.g. Assets/VFX/MyEffect.vfx)"] = None,
|
||||
overwrite: Annotated[Any,
|
||||
"[VFX] Overwrite existing asset (bool or string)"] = None,
|
||||
folder: Annotated[str | None,
|
||||
"[VFX] Folder to search for assets (for vfx_list_assets)"] = None,
|
||||
search: Annotated[str | None,
|
||||
"[VFX] Search pattern for assets (for vfx_list_assets)"] = None,
|
||||
|
||||
# Runtime parameters
|
||||
parameter: Annotated[str | None, "[VFX] Exposed parameter name"] = None,
|
||||
value: Annotated[Any,
|
||||
"[VFX] Parameter value (number, bool, array, or string)"] = None,
|
||||
texture_path: Annotated[str | None, "[VFX] Texture asset path"] = None,
|
||||
mesh_path: Annotated[str | None, "[VFX] Mesh asset path"] = None,
|
||||
gradient: Annotated[Any,
|
||||
"[VFX/Line/Trail] Gradient {colorKeys, alphaKeys} or {startColor, endColor} (dict or JSON string)"] = None,
|
||||
curve: Annotated[Any,
|
||||
"[VFX] Animation curve keys or {startValue, endValue} (array, dict, or JSON string)"] = None,
|
||||
event_name: Annotated[str | None, "[VFX] Event name to send"] = None,
|
||||
velocity: Annotated[Any,
|
||||
"[VFX] Event velocity [x,y,z] (array or JSON string)"] = None,
|
||||
size: Annotated[Any, "[VFX] Event size (number or string)"] = None,
|
||||
lifetime: Annotated[Any, "[VFX] Event lifetime (number or string)"] = None,
|
||||
play_rate: Annotated[Any,
|
||||
"[VFX] Playback speed multiplier (number or string)"] = None,
|
||||
seed: Annotated[Any, "[VFX] Random seed (integer or string)"] = None,
|
||||
reset_seed_on_play: Annotated[Any,
|
||||
"[VFX] Reset seed on play (bool or string)"] = None,
|
||||
|
||||
# === LINE/TRAIL RENDERER PARAMETERS ===
|
||||
positions: Annotated[Any,
|
||||
"[Line] Positions [[x,y,z], ...] (array or JSON string)"] = None,
|
||||
position: Annotated[Any,
|
||||
"[Line/Trail] Single position [x,y,z] (array or JSON string)"] = None,
|
||||
index: Annotated[Any, "[Line] Position index (integer or string)"] = None,
|
||||
|
||||
# Width
|
||||
width: Annotated[Any,
|
||||
"[Line/Trail] Uniform width (number or string)"] = None,
|
||||
start_width: Annotated[Any,
|
||||
"[Line/Trail] Start width (number or string)"] = None,
|
||||
end_width: Annotated[Any,
|
||||
"[Line/Trail] End width (number or string)"] = None,
|
||||
width_curve: Annotated[Any,
|
||||
"[Line/Trail] Width curve (number or dict)"] = None,
|
||||
width_multiplier: Annotated[Any,
|
||||
"[Line/Trail] Width multiplier (number or string)"] = None,
|
||||
|
||||
# Color
|
||||
color: Annotated[Any,
|
||||
"[Line/Trail/VFX] Color [r,g,b,a] (array or JSON string)"] = None,
|
||||
start_color_line: Annotated[Any,
|
||||
"[Line/Trail] Start color (array or JSON string)"] = None,
|
||||
end_color: Annotated[Any,
|
||||
"[Line/Trail] End color (array or JSON string)"] = None,
|
||||
|
||||
# Material & properties
|
||||
material_path: Annotated[str | None,
|
||||
"[Particle/Line/Trail] Material asset path"] = None,
|
||||
trail_material_path: Annotated[str | None,
|
||||
"[Particle] Trail material asset path"] = None,
|
||||
loop: Annotated[Any,
|
||||
"[Line] Connect end to start (bool or string)"] = None,
|
||||
use_world_space: Annotated[Any,
|
||||
"[Line] Use world space (bool or string)"] = None,
|
||||
num_corner_vertices: Annotated[Any,
|
||||
"[Line/Trail] Corner vertices (integer or string)"] = None,
|
||||
num_cap_vertices: Annotated[Any,
|
||||
"[Line/Trail] Cap vertices (integer or string)"] = None,
|
||||
alignment: Annotated[Literal["View", "Local", "TransformZ"]
|
||||
| None, "[Line/Trail] Alignment"] = None,
|
||||
texture_mode: Annotated[Literal["Stretch", "Tile", "DistributePerSegment",
|
||||
"RepeatPerSegment"] | None, "[Line/Trail] Texture mode"] = None,
|
||||
generate_lighting_data: Annotated[Any,
|
||||
"[Line/Trail] Generate lighting data for GI (bool or string)"] = None,
|
||||
sorting_order: Annotated[Any,
|
||||
"[Line/Trail/Particle] Sorting order (integer or string)"] = None,
|
||||
sorting_layer_name: Annotated[str | None,
|
||||
"[Renderer] Sorting layer name"] = None,
|
||||
sorting_layer_id: Annotated[Any,
|
||||
"[Renderer] Sorting layer ID (integer or string)"] = None,
|
||||
render_mode: Annotated[str | None,
|
||||
"[Particle] Render mode (Billboard, Stretch, HorizontalBillboard, VerticalBillboard, Mesh, None)"] = None,
|
||||
sort_mode: Annotated[str | None,
|
||||
"[Particle] Sort mode (None, Distance, OldestInFront, YoungestInFront, Depth)"] = None,
|
||||
|
||||
# === RENDERER COMMON PROPERTIES (Shadows, Lighting, Probes) ===
|
||||
shadow_casting_mode: Annotated[Literal["Off", "On", "TwoSided",
|
||||
"ShadowsOnly"] | None, "[Renderer] Shadow casting mode"] = None,
|
||||
receive_shadows: Annotated[Any,
|
||||
"[Renderer] Receive shadows (bool or string)"] = None,
|
||||
shadow_bias: Annotated[Any,
|
||||
"[Renderer] Shadow bias (number or string)"] = None,
|
||||
light_probe_usage: Annotated[Literal["Off", "BlendProbes", "UseProxyVolume",
|
||||
"CustomProvided"] | None, "[Renderer] Light probe usage mode"] = None,
|
||||
reflection_probe_usage: Annotated[Literal["Off", "BlendProbes", "BlendProbesAndSkybox",
|
||||
"Simple"] | None, "[Renderer] Reflection probe usage mode"] = None,
|
||||
motion_vector_generation_mode: Annotated[Literal["Camera", "Object",
|
||||
"ForceNoMotion"] | None, "[Renderer] Motion vector generation mode"] = None,
|
||||
rendering_layer_mask: Annotated[Any,
|
||||
"[Renderer] Rendering layer mask for SRP (integer or string)"] = None,
|
||||
|
||||
# === PARTICLE RENDERER SPECIFIC ===
|
||||
min_particle_size: Annotated[Any,
|
||||
"[Particle] Min particle size relative to viewport (number or string)"] = None,
|
||||
max_particle_size: Annotated[Any,
|
||||
"[Particle] Max particle size relative to viewport (number or string)"] = None,
|
||||
length_scale: Annotated[Any,
|
||||
"[Particle] Length scale for stretched billboard (number or string)"] = None,
|
||||
velocity_scale: Annotated[Any,
|
||||
"[Particle] Velocity scale for stretched billboard (number or string)"] = None,
|
||||
camera_velocity_scale: Annotated[Any,
|
||||
"[Particle] Camera velocity scale for stretched billboard (number or string)"] = None,
|
||||
normal_direction: Annotated[Any,
|
||||
"[Particle] Normal direction 0-1 (number or string)"] = None,
|
||||
pivot: Annotated[Any,
|
||||
"[Particle] Pivot offset [x,y,z] (array or JSON string)"] = None,
|
||||
flip: Annotated[Any,
|
||||
"[Particle] Flip [x,y,z] (array or JSON string)"] = None,
|
||||
allow_roll: Annotated[Any,
|
||||
"[Particle] Allow roll for mesh particles (bool or string)"] = None,
|
||||
|
||||
# Shape creation (line_create_*)
|
||||
start: Annotated[Any,
|
||||
"[Line] Start point [x,y,z] (array or JSON string)"] = None,
|
||||
end: Annotated[Any,
|
||||
"[Line] End point [x,y,z] (array or JSON string)"] = None,
|
||||
center: Annotated[Any,
|
||||
"[Line] Circle/arc center [x,y,z] (array or JSON string)"] = None,
|
||||
segments: Annotated[Any,
|
||||
"[Line] Number of segments (integer or string)"] = None,
|
||||
normal: Annotated[Any,
|
||||
"[Line] Normal direction [x,y,z] (array or JSON string)"] = None,
|
||||
start_angle: Annotated[Any,
|
||||
"[Line] Arc start angle degrees (number or string)"] = None,
|
||||
end_angle: Annotated[Any,
|
||||
"[Line] Arc end angle degrees (number or string)"] = None,
|
||||
control_point1: Annotated[Any,
|
||||
"[Line] Bezier control point 1 (array or JSON string)"] = None,
|
||||
control_point2: Annotated[Any,
|
||||
"[Line] Bezier control point 2 (cubic) (array or JSON string)"] = None,
|
||||
|
||||
# Trail specific
|
||||
min_vertex_distance: Annotated[Any,
|
||||
"[Trail] Min vertex distance (number or string)"] = None,
|
||||
autodestruct: Annotated[Any,
|
||||
"[Trail] Destroy when finished (bool or string)"] = None,
|
||||
emitting: Annotated[Any, "[Trail] Is emitting (bool or string)"] = None,
|
||||
|
||||
# Common vector params for shape/velocity
|
||||
x: Annotated[Any,
|
||||
"[Particle] Velocity X (number or MinMaxCurve dict)"] = None,
|
||||
y: Annotated[Any,
|
||||
"[Particle] Velocity Y (number or MinMaxCurve dict)"] = None,
|
||||
z: Annotated[Any,
|
||||
"[Particle] Velocity Z (number or MinMaxCurve dict)"] = None,
|
||||
speed_modifier: Annotated[Any,
|
||||
"[Particle] Speed modifier (number or MinMaxCurve dict)"] = None,
|
||||
space: Annotated[Literal["Local", "World"] |
|
||||
None, "[Particle] Velocity space"] = None,
|
||||
separate_axes: Annotated[Any,
|
||||
"[Particle] Separate XYZ axes (bool or string)"] = None,
|
||||
size_over_lifetime: Annotated[Any,
|
||||
"[Particle] Size over lifetime (number or MinMaxCurve dict)"] = None,
|
||||
size_x: Annotated[Any,
|
||||
"[Particle] Size X (number or MinMaxCurve dict)"] = None,
|
||||
size_y: Annotated[Any,
|
||||
"[Particle] Size Y (number or MinMaxCurve dict)"] = None,
|
||||
size_z: Annotated[Any,
|
||||
"[Particle] Size Z (number or MinMaxCurve dict)"] = None,
|
||||
|
||||
) -> dict[str, Any]:
|
||||
"""Unified VFX management tool."""
|
||||
|
||||
|
|
@ -437,294 +99,14 @@ async def manage_vfx(
|
|||
|
||||
unity_instance = get_unity_instance_from_context(ctx)
|
||||
|
||||
# Build parameters dict with normalized action to stay consistent with Unity
|
||||
params_dict: dict[str, Any] = {"action": action_normalized}
|
||||
|
||||
# Target
|
||||
if properties is not None:
|
||||
params_dict["properties"] = properties
|
||||
if target is not None:
|
||||
params_dict["target"] = target
|
||||
if search_method is not None:
|
||||
params_dict["searchMethod"] = search_method
|
||||
|
||||
# === PARTICLE SYSTEM ===
|
||||
# Pass through all values - C# side handles parsing (ParseColor, ParseVector3, ParseMinMaxCurve, ToObject<T>)
|
||||
if duration is not None:
|
||||
params_dict["duration"] = duration
|
||||
if looping is not None:
|
||||
params_dict["looping"] = looping
|
||||
if prewarm is not None:
|
||||
params_dict["prewarm"] = prewarm
|
||||
if start_delay is not None:
|
||||
params_dict["startDelay"] = start_delay
|
||||
if start_lifetime is not None:
|
||||
params_dict["startLifetime"] = start_lifetime
|
||||
if start_speed is not None:
|
||||
params_dict["startSpeed"] = start_speed
|
||||
if start_size is not None:
|
||||
params_dict["startSize"] = start_size
|
||||
if start_rotation is not None:
|
||||
params_dict["startRotation"] = start_rotation
|
||||
if start_color is not None:
|
||||
params_dict["startColor"] = start_color
|
||||
if gravity_modifier is not None:
|
||||
params_dict["gravityModifier"] = gravity_modifier
|
||||
if simulation_space is not None:
|
||||
params_dict["simulationSpace"] = simulation_space
|
||||
if scaling_mode is not None:
|
||||
params_dict["scalingMode"] = scaling_mode
|
||||
if play_on_awake is not None:
|
||||
params_dict["playOnAwake"] = play_on_awake
|
||||
if max_particles is not None:
|
||||
params_dict["maxParticles"] = max_particles
|
||||
|
||||
# Emission
|
||||
if rate_over_time is not None:
|
||||
params_dict["rateOverTime"] = rate_over_time
|
||||
if rate_over_distance is not None:
|
||||
params_dict["rateOverDistance"] = rate_over_distance
|
||||
|
||||
# Shape
|
||||
if shape_type is not None:
|
||||
params_dict["shapeType"] = shape_type
|
||||
if radius is not None:
|
||||
params_dict["radius"] = radius
|
||||
if radius_thickness is not None:
|
||||
params_dict["radiusThickness"] = radius_thickness
|
||||
if angle is not None:
|
||||
params_dict["angle"] = angle
|
||||
if arc is not None:
|
||||
params_dict["arc"] = arc
|
||||
|
||||
# Noise
|
||||
if strength is not None:
|
||||
params_dict["strength"] = strength
|
||||
if frequency is not None:
|
||||
params_dict["frequency"] = frequency
|
||||
if scroll_speed is not None:
|
||||
params_dict["scrollSpeed"] = scroll_speed
|
||||
if damping is not None:
|
||||
params_dict["damping"] = damping
|
||||
if octave_count is not None:
|
||||
params_dict["octaveCount"] = octave_count
|
||||
if quality is not None:
|
||||
params_dict["quality"] = quality
|
||||
|
||||
# Module
|
||||
if module is not None:
|
||||
params_dict["module"] = module
|
||||
if enabled is not None:
|
||||
params_dict["enabled"] = enabled
|
||||
|
||||
# Burst
|
||||
if time is not None:
|
||||
params_dict["time"] = time
|
||||
if count is not None:
|
||||
params_dict["count"] = count
|
||||
if min_count is not None:
|
||||
params_dict["minCount"] = min_count
|
||||
if max_count is not None:
|
||||
params_dict["maxCount"] = max_count
|
||||
if cycles is not None:
|
||||
params_dict["cycles"] = cycles
|
||||
if interval is not None:
|
||||
params_dict["interval"] = interval
|
||||
if probability is not None:
|
||||
params_dict["probability"] = probability
|
||||
|
||||
# Playback
|
||||
if with_children is not None:
|
||||
params_dict["withChildren"] = with_children
|
||||
|
||||
# === VFX GRAPH ===
|
||||
# Asset management parameters
|
||||
if asset_name is not None:
|
||||
params_dict["assetName"] = asset_name
|
||||
if folder_path is not None:
|
||||
params_dict["folderPath"] = folder_path
|
||||
if template is not None:
|
||||
params_dict["template"] = template
|
||||
if asset_path is not None:
|
||||
params_dict["assetPath"] = asset_path
|
||||
if overwrite is not None:
|
||||
params_dict["overwrite"] = overwrite
|
||||
if folder is not None:
|
||||
params_dict["folder"] = folder
|
||||
if search is not None:
|
||||
params_dict["search"] = search
|
||||
|
||||
# Runtime parameters
|
||||
if parameter is not None:
|
||||
params_dict["parameter"] = parameter
|
||||
if value is not None:
|
||||
params_dict["value"] = value
|
||||
if texture_path is not None:
|
||||
params_dict["texturePath"] = texture_path
|
||||
if mesh_path is not None:
|
||||
params_dict["meshPath"] = mesh_path
|
||||
if gradient is not None:
|
||||
params_dict["gradient"] = gradient
|
||||
if curve is not None:
|
||||
params_dict["curve"] = curve
|
||||
if event_name is not None:
|
||||
params_dict["eventName"] = event_name
|
||||
if velocity is not None:
|
||||
params_dict["velocity"] = velocity
|
||||
if size is not None:
|
||||
params_dict["size"] = size
|
||||
if lifetime is not None:
|
||||
params_dict["lifetime"] = lifetime
|
||||
if play_rate is not None:
|
||||
params_dict["playRate"] = play_rate
|
||||
if seed is not None:
|
||||
params_dict["seed"] = seed
|
||||
if reset_seed_on_play is not None:
|
||||
params_dict["resetSeedOnPlay"] = reset_seed_on_play
|
||||
|
||||
# === LINE/TRAIL RENDERER ===
|
||||
if positions is not None:
|
||||
params_dict["positions"] = positions
|
||||
if position is not None:
|
||||
params_dict["position"] = position
|
||||
if index is not None:
|
||||
params_dict["index"] = index
|
||||
|
||||
# Width
|
||||
if width is not None:
|
||||
params_dict["width"] = width
|
||||
if start_width is not None:
|
||||
params_dict["startWidth"] = start_width
|
||||
if end_width is not None:
|
||||
params_dict["endWidth"] = end_width
|
||||
if width_curve is not None:
|
||||
params_dict["widthCurve"] = width_curve
|
||||
if width_multiplier is not None:
|
||||
params_dict["widthMultiplier"] = width_multiplier
|
||||
|
||||
# Color
|
||||
if color is not None:
|
||||
params_dict["color"] = color
|
||||
if start_color_line is not None:
|
||||
params_dict["startColor"] = start_color_line
|
||||
if end_color is not None:
|
||||
params_dict["endColor"] = end_color
|
||||
|
||||
# Material & properties
|
||||
if material_path is not None:
|
||||
params_dict["materialPath"] = material_path
|
||||
if trail_material_path is not None:
|
||||
params_dict["trailMaterialPath"] = trail_material_path
|
||||
if loop is not None:
|
||||
params_dict["loop"] = loop
|
||||
if use_world_space is not None:
|
||||
params_dict["useWorldSpace"] = use_world_space
|
||||
if num_corner_vertices is not None:
|
||||
params_dict["numCornerVertices"] = num_corner_vertices
|
||||
if num_cap_vertices is not None:
|
||||
params_dict["numCapVertices"] = num_cap_vertices
|
||||
if alignment is not None:
|
||||
params_dict["alignment"] = alignment
|
||||
if texture_mode is not None:
|
||||
params_dict["textureMode"] = texture_mode
|
||||
if generate_lighting_data is not None:
|
||||
params_dict["generateLightingData"] = generate_lighting_data
|
||||
if sorting_order is not None:
|
||||
params_dict["sortingOrder"] = sorting_order
|
||||
if sorting_layer_name is not None:
|
||||
params_dict["sortingLayerName"] = sorting_layer_name
|
||||
if sorting_layer_id is not None:
|
||||
params_dict["sortingLayerID"] = sorting_layer_id
|
||||
if render_mode is not None:
|
||||
params_dict["renderMode"] = render_mode
|
||||
if sort_mode is not None:
|
||||
params_dict["sortMode"] = sort_mode
|
||||
|
||||
# Renderer common properties (shadows, lighting, probes)
|
||||
if shadow_casting_mode is not None:
|
||||
params_dict["shadowCastingMode"] = shadow_casting_mode
|
||||
if receive_shadows is not None:
|
||||
params_dict["receiveShadows"] = receive_shadows
|
||||
if shadow_bias is not None:
|
||||
params_dict["shadowBias"] = shadow_bias
|
||||
if light_probe_usage is not None:
|
||||
params_dict["lightProbeUsage"] = light_probe_usage
|
||||
if reflection_probe_usage is not None:
|
||||
params_dict["reflectionProbeUsage"] = reflection_probe_usage
|
||||
if motion_vector_generation_mode is not None:
|
||||
params_dict["motionVectorGenerationMode"] = motion_vector_generation_mode
|
||||
if rendering_layer_mask is not None:
|
||||
params_dict["renderingLayerMask"] = rendering_layer_mask
|
||||
|
||||
# Particle renderer specific
|
||||
if min_particle_size is not None:
|
||||
params_dict["minParticleSize"] = min_particle_size
|
||||
if max_particle_size is not None:
|
||||
params_dict["maxParticleSize"] = max_particle_size
|
||||
if length_scale is not None:
|
||||
params_dict["lengthScale"] = length_scale
|
||||
if velocity_scale is not None:
|
||||
params_dict["velocityScale"] = velocity_scale
|
||||
if camera_velocity_scale is not None:
|
||||
params_dict["cameraVelocityScale"] = camera_velocity_scale
|
||||
if normal_direction is not None:
|
||||
params_dict["normalDirection"] = normal_direction
|
||||
if pivot is not None:
|
||||
params_dict["pivot"] = pivot
|
||||
if flip is not None:
|
||||
params_dict["flip"] = flip
|
||||
if allow_roll is not None:
|
||||
params_dict["allowRoll"] = allow_roll
|
||||
|
||||
# Shape creation
|
||||
if start is not None:
|
||||
params_dict["start"] = start
|
||||
if end is not None:
|
||||
params_dict["end"] = end
|
||||
if center is not None:
|
||||
params_dict["center"] = center
|
||||
if segments is not None:
|
||||
params_dict["segments"] = segments
|
||||
if normal is not None:
|
||||
params_dict["normal"] = normal
|
||||
if start_angle is not None:
|
||||
params_dict["startAngle"] = start_angle
|
||||
if end_angle is not None:
|
||||
params_dict["endAngle"] = end_angle
|
||||
if control_point1 is not None:
|
||||
params_dict["controlPoint1"] = control_point1
|
||||
if control_point2 is not None:
|
||||
params_dict["controlPoint2"] = control_point2
|
||||
|
||||
# Trail specific
|
||||
if min_vertex_distance is not None:
|
||||
params_dict["minVertexDistance"] = min_vertex_distance
|
||||
if autodestruct is not None:
|
||||
params_dict["autodestruct"] = autodestruct
|
||||
if emitting is not None:
|
||||
params_dict["emitting"] = emitting
|
||||
|
||||
# Velocity/size axes
|
||||
if x is not None:
|
||||
params_dict["x"] = x
|
||||
if y is not None:
|
||||
params_dict["y"] = y
|
||||
if z is not None:
|
||||
params_dict["z"] = z
|
||||
if speed_modifier is not None:
|
||||
params_dict["speedModifier"] = speed_modifier
|
||||
if space is not None:
|
||||
params_dict["space"] = space
|
||||
if separate_axes is not None:
|
||||
params_dict["separateAxes"] = separate_axes
|
||||
if size_over_lifetime is not None:
|
||||
params_dict["size"] = size_over_lifetime
|
||||
if size_x is not None:
|
||||
params_dict["sizeX"] = size_x
|
||||
if size_y is not None:
|
||||
params_dict["sizeY"] = size_y
|
||||
if size_z is not None:
|
||||
params_dict["sizeZ"] = size_z
|
||||
|
||||
# Remove None values
|
||||
params_dict = {k: v for k, v in params_dict.items() if v is not None}
|
||||
|
||||
# Send to Unity
|
||||
|
|
|
|||
Loading…
Reference in New Issue