升级XR插件版本

This commit is contained in:
Sora丶kong
2026-03-02 17:56:21 +08:00
parent 8962657674
commit 60f512a9bc
1317 changed files with 110305 additions and 48249 deletions

View File

@@ -0,0 +1,220 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class LineCreate
{
public static object CreateLine(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
Vector3 start = ManageVfxCommon.ParseVector3(@params["start"]);
Vector3 end = ManageVfxCommon.ParseVector3(@params["end"]);
Undo.RecordObject(lr, "Create Line");
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" };
}
public static object CreateCircle(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
Vector3 center = ManageVfxCommon.ParseVector3(@params["center"]);
float radius = @params["radius"]?.ToObject<float>() ?? 1f;
int segments = @params["segments"]?.ToObject<int>() ?? 32;
Vector3 normal = @params["normal"] != null ? ManageVfxCommon.ParseVector3(@params["normal"]).normalized : Vector3.up;
Vector3 right = Vector3.Cross(normal, Vector3.forward);
if (right.sqrMagnitude < 0.001f) right = Vector3.Cross(normal, Vector3.up);
right = right.normalized;
Vector3 forward = Vector3.Cross(right, normal).normalized;
Undo.RecordObject(lr, "Create Circle");
lr.positionCount = segments;
lr.loop = true;
for (int i = 0; i < segments; i++)
{
float angle = (float)i / segments * Mathf.PI * 2f;
Vector3 point = center + (right * Mathf.Cos(angle) + forward * Mathf.Sin(angle)) * radius;
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" };
}
public static object CreateArc(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
Vector3 center = ManageVfxCommon.ParseVector3(@params["center"]);
float radius = @params["radius"]?.ToObject<float>() ?? 1f;
float startAngle = (@params["startAngle"]?.ToObject<float>() ?? 0f) * Mathf.Deg2Rad;
float endAngle = (@params["endAngle"]?.ToObject<float>() ?? 180f) * Mathf.Deg2Rad;
int segments = @params["segments"]?.ToObject<int>() ?? 16;
Vector3 normal = @params["normal"] != null ? ManageVfxCommon.ParseVector3(@params["normal"]).normalized : Vector3.up;
Vector3 right = Vector3.Cross(normal, Vector3.forward);
if (right.sqrMagnitude < 0.001f) right = Vector3.Cross(normal, Vector3.up);
right = right.normalized;
Vector3 forward = Vector3.Cross(right, normal).normalized;
Undo.RecordObject(lr, "Create Arc");
lr.positionCount = segments + 1;
lr.loop = false;
for (int i = 0; i <= segments; i++)
{
float t = (float)i / segments;
float angle = Mathf.Lerp(startAngle, endAngle, t);
Vector3 point = center + (right * Mathf.Cos(angle) + forward * Mathf.Sin(angle)) * radius;
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" };
}
public static object CreateBezier(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
Vector3 start = ManageVfxCommon.ParseVector3(@params["start"]);
Vector3 end = ManageVfxCommon.ParseVector3(@params["end"]);
Vector3 cp1 = ManageVfxCommon.ParseVector3(@params["controlPoint1"] ?? @params["control1"]);
Vector3 cp2 = @params["controlPoint2"] != null || @params["control2"] != null
? ManageVfxCommon.ParseVector3(@params["controlPoint2"] ?? @params["control2"])
: cp1;
int segments = @params["segments"]?.ToObject<int>() ?? 32;
bool isQuadratic = @params["controlPoint2"] == null && @params["control2"] == null;
Undo.RecordObject(lr, "Create Bezier");
lr.positionCount = segments + 1;
lr.loop = false;
for (int i = 0; i <= segments; i++)
{
float t = (float)i / segments;
Vector3 point;
if (isQuadratic)
{
float u = 1 - t;
point = u * u * start + 2 * u * t * cp1 + t * t * end;
}
else
{
float u = 1 - t;
point = u * u * u * start + 3 * u * u * t * cp1 + 3 * u * t * t * cp2 + t * t * t * end;
}
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" };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d553d3837ecc4d999225bc9b3160a26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class LineRead
{
public static LineRenderer FindLineRenderer(JObject @params)
{
GameObject go = ManageVfxCommon.FindTargetGameObject(@params);
return go?.GetComponent<LineRenderer>();
}
public static object GetInfo(JObject @params)
{
LineRenderer lr = FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
var positions = new Vector3[lr.positionCount];
lr.GetPositions(positions);
return new
{
success = true,
data = new
{
gameObject = lr.gameObject.name,
positionCount = lr.positionCount,
positions = positions.Select(p => new { x = p.x, y = p.y, z = p.z }).ToArray(),
startWidth = lr.startWidth,
endWidth = lr.endWidth,
loop = lr.loop,
useWorldSpace = lr.useWorldSpace,
alignment = lr.alignment.ToString(),
textureMode = lr.textureMode.ToString(),
numCornerVertices = lr.numCornerVertices,
numCapVertices = lr.numCapVertices,
generateLightingData = lr.generateLightingData,
material = lr.sharedMaterial?.name,
shadowCastingMode = lr.shadowCastingMode.ToString(),
receiveShadows = lr.receiveShadows,
lightProbeUsage = lr.lightProbeUsage.ToString(),
reflectionProbeUsage = lr.reflectionProbeUsage.ToString(),
sortingOrder = lr.sortingOrder,
sortingLayerName = lr.sortingLayerName,
renderingLayerMask = lr.renderingLayerMask
}
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df77cf0ca14344b0cb2f1b84c5eb15e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,189 @@
using System.Collections.Generic;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class LineWrite
{
public static object SetPositions(JObject @params)
{
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" };
var positions = new Vector3[posArr.Count];
for (int i = 0; i < posArr.Count; i++)
{
positions[i] = ManageVfxCommon.ParseVector3(posArr[i]);
}
Undo.RecordObject(lr, "Set Line Positions");
lr.positionCount = positions.Length;
lr.SetPositions(positions);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Set {positions.Length} positions" };
}
public static object AddPosition(JObject @params)
{
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");
int idx = lr.positionCount;
lr.positionCount = idx + 1;
lr.SetPosition(idx, pos);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Added position at index {idx}", index = idx };
}
public static object SetPosition(JObject @params)
{
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}" };
Vector3 pos = ManageVfxCommon.ParseVector3(@params["position"]);
Undo.RecordObject(lr, "Set Line Position");
lr.SetPosition(index, pos);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Set position at index {index}" };
}
public static object SetWidth(JObject @params)
{
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>();
RendererHelpers.ApplyWidthProperties(@params, changes,
v => lr.startWidth = v, v => lr.endWidth = v,
v => lr.widthCurve = v, v => lr.widthMultiplier = v,
ManageVfxCommon.ParseAnimationCurve);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetColor(JObject @params)
{
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>();
RendererHelpers.ApplyColorProperties(@params, changes,
v => lr.startColor = v, v => lr.endColor = v,
v => lr.colorGradient = v,
ManageVfxCommon.ParseColor, ManageVfxCommon.ParseGradient, fadeEndAlpha: false);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetMaterial(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
return RendererHelpers.SetRendererMaterial(lr, @params, "Set Line Material", ManageVfxCommon.FindMaterialByPath);
}
public static object SetProperties(JObject @params)
{
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,
v => lr.alignment = v, v => lr.textureMode = v,
v => lr.generateLightingData = v);
RendererHelpers.ApplyCommonRendererProperties(lr, @params, changes);
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object Clear(JObject @params)
{
LineRenderer lr = LineRead.FindLineRenderer(@params);
if (lr == null) return new { success = false, message = "LineRenderer not found" };
int count = lr.positionCount;
Undo.RecordObject(lr, "Clear Line");
lr.positionCount = 0;
EditorUtility.SetDirty(lr);
return new { success = true, message = $"Cleared {count} positions" };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3911acc5a6a6a494cb88a647e0426d67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,412 @@
using System;
using System.Collections.Generic;
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
{
/// <summary>
/// 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.
/// </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) => StringCaseUtility.ToCamelCase(key);
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 VfxGraphAssets.CreateAsset(@params);
case "assign_asset": return VfxGraphAssets.AssignAsset(@params);
case "list_templates": return VfxGraphAssets.ListTemplates(@params);
case "list_assets": return VfxGraphAssets.ListAssets(@params);
// Runtime parameter control
case "get_info": return VfxGraphRead.GetInfo(@params);
case "set_float": return VfxGraphWrite.SetParameter<float>(@params, (vfx, n, v) => vfx.SetFloat(n, v));
case "set_int": return VfxGraphWrite.SetParameter<int>(@params, (vfx, n, v) => vfx.SetInt(n, v));
case "set_bool": return VfxGraphWrite.SetParameter<bool>(@params, (vfx, n, v) => vfx.SetBool(n, v));
case "set_vector2": return VfxGraphWrite.SetVector(@params, 2);
case "set_vector3": return VfxGraphWrite.SetVector(@params, 3);
case "set_vector4": return VfxGraphWrite.SetVector(@params, 4);
case "set_color": return VfxGraphWrite.SetColor(@params);
case "set_gradient": return VfxGraphWrite.SetGradient(@params);
case "set_texture": return VfxGraphWrite.SetTexture(@params);
case "set_mesh": return VfxGraphWrite.SetMesh(@params);
case "set_curve": return VfxGraphWrite.SetCurve(@params);
case "send_event": return VfxGraphWrite.SendEvent(@params);
case "play": return VfxGraphControl.Control(@params, "play");
case "stop": return VfxGraphControl.Control(@params, "stop");
case "pause": return VfxGraphControl.Control(@params, "pause");
case "reinit": return VfxGraphControl.Control(@params, "reinit");
case "set_playback_speed": return VfxGraphControl.SetPlaybackSpeed(@params);
case "set_seed": return VfxGraphControl.SetSeed(@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
}
#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" };
}
}
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a8f3d2c1e9b74f6a8c5d0e2f1a3b4c5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Helpers;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class ManageVfxCommon
{
public static Color ParseColor(JToken token) => VectorParsing.ParseColorOrDefault(token);
public static Vector3 ParseVector3(JToken token) => VectorParsing.ParseVector3OrDefault(token);
public static Vector4 ParseVector4(JToken token) => VectorParsing.ParseVector4OrDefault(token);
public static Gradient ParseGradient(JToken token) => VectorParsing.ParseGradientOrDefault(token);
public static AnimationCurve ParseAnimationCurve(JToken token, float defaultValue = 1f)
=> VectorParsing.ParseAnimationCurveOrDefault(token, defaultValue);
public static GameObject FindTargetGameObject(JObject @params)
=> ObjectResolver.ResolveGameObject(@params["target"], @params["searchMethod"]?.ToString());
public static Material FindMaterialByPath(string path)
=> ObjectResolver.ResolveMaterial(path);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c5e603b26d2f47529394c1ec6b8ed79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,87 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class ParticleCommon
{
public static ParticleSystem FindParticleSystem(JObject @params)
{
GameObject go = ManageVfxCommon.FindTargetGameObject(@params);
return go?.GetComponent<ParticleSystem>();
}
public static ParticleSystem.MinMaxCurve ParseMinMaxCurve(JToken token, float defaultValue = 1f)
{
if (token == null)
return new ParticleSystem.MinMaxCurve(defaultValue);
if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
{
return new ParticleSystem.MinMaxCurve(token.ToObject<float>());
}
if (token is JObject obj)
{
string mode = obj["mode"]?.ToString()?.ToLowerInvariant() ?? "constant";
switch (mode)
{
case "constant":
float constant = obj["value"]?.ToObject<float>() ?? defaultValue;
return new ParticleSystem.MinMaxCurve(constant);
case "random_between_constants":
case "two_constants":
float min = obj["min"]?.ToObject<float>() ?? 0f;
float max = obj["max"]?.ToObject<float>() ?? 1f;
return new ParticleSystem.MinMaxCurve(min, max);
case "curve":
AnimationCurve curve = ManageVfxCommon.ParseAnimationCurve(obj, defaultValue);
return new ParticleSystem.MinMaxCurve(obj["multiplier"]?.ToObject<float>() ?? 1f, curve);
default:
return new ParticleSystem.MinMaxCurve(defaultValue);
}
}
return new ParticleSystem.MinMaxCurve(defaultValue);
}
public static ParticleSystem.MinMaxGradient ParseMinMaxGradient(JToken token)
{
if (token == null)
return new ParticleSystem.MinMaxGradient(Color.white);
if (token is JArray arr && arr.Count >= 3)
{
return new ParticleSystem.MinMaxGradient(ManageVfxCommon.ParseColor(arr));
}
if (token is JObject obj)
{
string mode = obj["mode"]?.ToString()?.ToLowerInvariant() ?? "color";
switch (mode)
{
case "color":
return new ParticleSystem.MinMaxGradient(ManageVfxCommon.ParseColor(obj["color"]));
case "two_colors":
Color colorMin = ManageVfxCommon.ParseColor(obj["colorMin"]);
Color colorMax = ManageVfxCommon.ParseColor(obj["colorMax"]);
return new ParticleSystem.MinMaxGradient(colorMin, colorMax);
case "gradient":
return new ParticleSystem.MinMaxGradient(ManageVfxCommon.ParseGradient(obj));
default:
return new ParticleSystem.MinMaxGradient(Color.white);
}
}
return new ParticleSystem.MinMaxGradient(Color.white);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3a91aa6f6b9c4121a2ccc1a8147bbf9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,121 @@
using System;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class ParticleControl
{
public static object EnableModule(JObject @params)
{
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
string moduleName = @params["module"]?.ToString()?.ToLowerInvariant();
bool enabled = @params["enabled"]?.ToObject<bool>() ?? true;
if (string.IsNullOrEmpty(moduleName)) return new { success = false, message = "Module name required" };
Undo.RecordObject(ps, $"Toggle {moduleName}");
switch (moduleName.Replace("_", ""))
{
case "emission": var em = ps.emission; em.enabled = enabled; break;
case "shape": var sh = ps.shape; sh.enabled = enabled; break;
case "coloroverlifetime": var col = ps.colorOverLifetime; col.enabled = enabled; break;
case "sizeoverlifetime": var sol = ps.sizeOverLifetime; sol.enabled = enabled; break;
case "velocityoverlifetime": var vol = ps.velocityOverLifetime; vol.enabled = enabled; break;
case "noise": var n = ps.noise; n.enabled = enabled; break;
case "collision": var coll = ps.collision; coll.enabled = enabled; break;
case "trails": var tr = ps.trails; tr.enabled = enabled; break;
case "lights": var li = ps.lights; li.enabled = enabled; break;
default: return new { success = false, message = $"Unknown module: {moduleName}" };
}
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Module '{moduleName}' {(enabled ? "enabled" : "disabled")}" };
}
public static object Control(JObject @params, string action)
{
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)
{
case "play": ps.Play(withChildren); break;
case "stop": ps.Stop(withChildren, ParticleSystemStopBehavior.StopEmitting); break;
case "pause": ps.Pause(withChildren); break;
case "restart": ps.Stop(withChildren, ParticleSystemStopBehavior.StopEmittingAndClear); ps.Play(withChildren); break;
case "clear": ps.Clear(withChildren); break;
default: return new { success = false, message = $"Unknown action: {action}" };
}
return new { success = true, message = $"ParticleSystem {action}" };
}
public static object AddBurst(JObject @params)
{
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;
float time = @params["time"]?.ToObject<float>() ?? 0f;
int minCountRaw = @params["minCount"]?.ToObject<int>() ?? @params["count"]?.ToObject<int>() ?? 30;
int maxCountRaw = @params["maxCount"]?.ToObject<int>() ?? @params["count"]?.ToObject<int>() ?? 30;
short minCount = (short)Math.Clamp(minCountRaw, 0, short.MaxValue);
short maxCount = (short)Math.Clamp(maxCountRaw, 0, short.MaxValue);
int cycles = @params["cycles"]?.ToObject<int>() ?? 1;
float interval = @params["interval"]?.ToObject<float>() ?? 0.01f;
var burst = new ParticleSystem.Burst(time, minCount, maxCount, cycles, interval);
burst.probability = @params["probability"]?.ToObject<float>() ?? 1f;
int idx = emission.burstCount;
var bursts = new ParticleSystem.Burst[idx + 1];
emission.GetBursts(bursts);
bursts[idx] = burst;
emission.SetBursts(bursts);
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Added burst at t={time}", burstIndex = idx };
}
public static object ClearBursts(JObject @params)
{
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
Undo.RecordObject(ps, "Clear Bursts");
var emission = ps.emission;
int count = emission.burstCount;
emission.SetBursts(new ParticleSystem.Burst[0]);
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Cleared {count} bursts" };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04e1bfb655f184337943edd5a3fbbcdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,153 @@
using Newtonsoft.Json.Linq;
using System.Linq;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class ParticleRead
{
private static object SerializeAnimationCurve(AnimationCurve curve)
{
if (curve == null)
{
return null;
}
return new
{
keys = curve.keys.Select(k => new
{
time = k.time,
value = k.value,
inTangent = k.inTangent,
outTangent = k.outTangent
}).ToArray()
};
}
private static object SerializeMinMaxCurve(ParticleSystem.MinMaxCurve curve)
{
switch (curve.mode)
{
case ParticleSystemCurveMode.Constant:
return new
{
mode = "constant",
value = curve.constant
};
case ParticleSystemCurveMode.TwoConstants:
return new
{
mode = "two_constants",
min = curve.constantMin,
max = curve.constantMax
};
case ParticleSystemCurveMode.Curve:
return new
{
mode = "curve",
multiplier = curve.curveMultiplier,
keys = curve.curve.keys.Select(k => new
{
time = k.time,
value = k.value,
inTangent = k.inTangent,
outTangent = k.outTangent
}).ToArray()
};
case ParticleSystemCurveMode.TwoCurves:
return new
{
mode = "curve",
multiplier = curve.curveMultiplier,
keys = curve.curveMax.keys.Select(k => new
{
time = k.time,
value = k.value,
inTangent = k.inTangent,
outTangent = k.outTangent
}).ToArray(),
originalMode = "two_curves",
curveMin = SerializeAnimationCurve(curve.curveMin),
curveMax = SerializeAnimationCurve(curve.curveMax)
};
default:
return new
{
mode = "constant",
value = curve.constant
};
}
}
public static object GetInfo(JObject @params)
{
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
if (ps == null)
{
return new { success = false, message = "ParticleSystem not found" };
}
var main = ps.main;
var emission = ps.emission;
var shape = ps.shape;
var renderer = ps.GetComponent<ParticleSystemRenderer>();
return new
{
success = true,
data = new
{
gameObject = ps.gameObject.name,
isPlaying = ps.isPlaying,
isPaused = ps.isPaused,
particleCount = ps.particleCount,
main = new
{
duration = main.duration,
looping = main.loop,
startLifetime = SerializeMinMaxCurve(main.startLifetime),
startSpeed = SerializeMinMaxCurve(main.startSpeed),
startSize = SerializeMinMaxCurve(main.startSize),
gravityModifier = SerializeMinMaxCurve(main.gravityModifier),
simulationSpace = main.simulationSpace.ToString(),
maxParticles = main.maxParticles
},
emission = new
{
enabled = emission.enabled,
rateOverTime = SerializeMinMaxCurve(emission.rateOverTime),
burstCount = emission.burstCount
},
shape = new
{
enabled = shape.enabled,
shapeType = shape.shapeType.ToString(),
radius = shape.radius,
angle = shape.angle
},
renderer = renderer != null ? new
{
renderMode = renderer.renderMode.ToString(),
sortMode = renderer.sortMode.ToString(),
material = renderer.sharedMaterial?.name,
trailMaterial = renderer.trailMaterial?.name,
minParticleSize = renderer.minParticleSize,
maxParticleSize = renderer.maxParticleSize,
shadowCastingMode = renderer.shadowCastingMode.ToString(),
receiveShadows = renderer.receiveShadows,
lightProbeUsage = renderer.lightProbeUsage.ToString(),
reflectionProbeUsage = renderer.reflectionProbeUsage.ToString(),
sortingOrder = renderer.sortingOrder,
sortingLayerName = renderer.sortingLayerName,
renderingLayerMask = renderer.renderingLayerMask
} : null
}
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74bb7c48a4e1944bcba43b3619653cb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,295 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class ParticleWrite
{
public static object SetMain(JObject @params)
{
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>();
if (@params["duration"] != null) { main.duration = @params["duration"].ToObject<float>(); changes.Add("duration"); }
if (@params["looping"] != null) { main.loop = @params["looping"].ToObject<bool>(); changes.Add("looping"); }
if (@params["prewarm"] != null) { main.prewarm = @params["prewarm"].ToObject<bool>(); changes.Add("prewarm"); }
if (@params["startDelay"] != null) { main.startDelay = ParticleCommon.ParseMinMaxCurve(@params["startDelay"], 0f); changes.Add("startDelay"); }
if (@params["startLifetime"] != null) { main.startLifetime = ParticleCommon.ParseMinMaxCurve(@params["startLifetime"], 5f); changes.Add("startLifetime"); }
if (@params["startSpeed"] != null) { main.startSpeed = ParticleCommon.ParseMinMaxCurve(@params["startSpeed"], 5f); changes.Add("startSpeed"); }
if (@params["startSize"] != null) { main.startSize = ParticleCommon.ParseMinMaxCurve(@params["startSize"], 1f); changes.Add("startSize"); }
if (@params["startRotation"] != null) { main.startRotation = ParticleCommon.ParseMinMaxCurve(@params["startRotation"], 0f); changes.Add("startRotation"); }
if (@params["startColor"] != null) { main.startColor = ParticleCommon.ParseMinMaxGradient(@params["startColor"]); changes.Add("startColor"); }
if (@params["gravityModifier"] != null) { main.gravityModifier = ParticleCommon.ParseMinMaxCurve(@params["gravityModifier"], 0f); changes.Add("gravityModifier"); }
if (@params["simulationSpace"] != null && Enum.TryParse<ParticleSystemSimulationSpace>(@params["simulationSpace"].ToString(), true, out var simSpace)) { main.simulationSpace = simSpace; changes.Add("simulationSpace"); }
if (@params["scalingMode"] != null && Enum.TryParse<ParticleSystemScalingMode>(@params["scalingMode"].ToString(), true, out var scaleMode)) { main.scalingMode = scaleMode; changes.Add("scalingMode"); }
if (@params["playOnAwake"] != null) { main.playOnAwake = @params["playOnAwake"].ToObject<bool>(); changes.Add("playOnAwake"); }
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)}" };
}
public static object SetEmission(JObject @params)
{
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>();
if (@params["enabled"] != null) { emission.enabled = @params["enabled"].ToObject<bool>(); changes.Add("enabled"); }
if (@params["rateOverTime"] != null) { emission.rateOverTime = ParticleCommon.ParseMinMaxCurve(@params["rateOverTime"], 10f); changes.Add("rateOverTime"); }
if (@params["rateOverDistance"] != null) { emission.rateOverDistance = ParticleCommon.ParseMinMaxCurve(@params["rateOverDistance"], 0f); changes.Add("rateOverDistance"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated emission: {string.Join(", ", changes)}" };
}
public static object SetShape(JObject @params)
{
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>();
if (@params["enabled"] != null) { shape.enabled = @params["enabled"].ToObject<bool>(); changes.Add("enabled"); }
if (@params["shapeType"] != null && Enum.TryParse<ParticleSystemShapeType>(@params["shapeType"].ToString(), true, out var shapeType)) { shape.shapeType = shapeType; changes.Add("shapeType"); }
if (@params["radius"] != null) { shape.radius = @params["radius"].ToObject<float>(); changes.Add("radius"); }
if (@params["radiusThickness"] != null) { shape.radiusThickness = @params["radiusThickness"].ToObject<float>(); changes.Add("radiusThickness"); }
if (@params["angle"] != null) { shape.angle = @params["angle"].ToObject<float>(); changes.Add("angle"); }
if (@params["arc"] != null) { shape.arc = @params["arc"].ToObject<float>(); changes.Add("arc"); }
if (@params["position"] != null) { shape.position = ManageVfxCommon.ParseVector3(@params["position"]); changes.Add("position"); }
if (@params["rotation"] != null) { shape.rotation = ManageVfxCommon.ParseVector3(@params["rotation"]); changes.Add("rotation"); }
if (@params["scale"] != null) { shape.scale = ManageVfxCommon.ParseVector3(@params["scale"]); changes.Add("scale"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated shape: {string.Join(", ", changes)}" };
}
public static object SetColorOverLifetime(JObject @params)
{
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>();
if (@params["enabled"] != null) { col.enabled = @params["enabled"].ToObject<bool>(); changes.Add("enabled"); }
if (@params["color"] != null) { col.color = ParticleCommon.ParseMinMaxGradient(@params["color"]); changes.Add("color"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetSizeOverLifetime(JObject @params)
{
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>();
bool hasSizeProperty = @params["size"] != null || @params["sizeX"] != null ||
@params["sizeY"] != null || @params["sizeZ"] != null;
if (hasSizeProperty && @params["enabled"] == null && !sol.enabled)
{
sol.enabled = true;
changes.Add("enabled");
}
else if (@params["enabled"] != null)
{
sol.enabled = @params["enabled"].ToObject<bool>();
changes.Add("enabled");
}
if (@params["separateAxes"] != null) { sol.separateAxes = @params["separateAxes"].ToObject<bool>(); changes.Add("separateAxes"); }
if (@params["size"] != null) { sol.size = ParticleCommon.ParseMinMaxCurve(@params["size"], 1f); changes.Add("size"); }
if (@params["sizeX"] != null) { sol.x = ParticleCommon.ParseMinMaxCurve(@params["sizeX"], 1f); changes.Add("sizeX"); }
if (@params["sizeY"] != null) { sol.y = ParticleCommon.ParseMinMaxCurve(@params["sizeY"], 1f); changes.Add("sizeY"); }
if (@params["sizeZ"] != null) { sol.z = ParticleCommon.ParseMinMaxCurve(@params["sizeZ"], 1f); changes.Add("sizeZ"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetVelocityOverLifetime(JObject @params)
{
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>();
if (@params["enabled"] != null) { vol.enabled = @params["enabled"].ToObject<bool>(); changes.Add("enabled"); }
if (@params["space"] != null && Enum.TryParse<ParticleSystemSimulationSpace>(@params["space"].ToString(), true, out var space)) { vol.space = space; changes.Add("space"); }
if (@params["x"] != null) { vol.x = ParticleCommon.ParseMinMaxCurve(@params["x"], 0f); changes.Add("x"); }
if (@params["y"] != null) { vol.y = ParticleCommon.ParseMinMaxCurve(@params["y"], 0f); changes.Add("y"); }
if (@params["z"] != null) { vol.z = ParticleCommon.ParseMinMaxCurve(@params["z"], 0f); changes.Add("z"); }
if (@params["speedModifier"] != null) { vol.speedModifier = ParticleCommon.ParseMinMaxCurve(@params["speedModifier"], 1f); changes.Add("speedModifier"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetNoise(JObject @params)
{
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>();
if (@params["enabled"] != null) { noise.enabled = @params["enabled"].ToObject<bool>(); changes.Add("enabled"); }
if (@params["strength"] != null) { noise.strength = ParticleCommon.ParseMinMaxCurve(@params["strength"], 1f); changes.Add("strength"); }
if (@params["frequency"] != null) { noise.frequency = @params["frequency"].ToObject<float>(); changes.Add("frequency"); }
if (@params["scrollSpeed"] != null) { noise.scrollSpeed = ParticleCommon.ParseMinMaxCurve(@params["scrollSpeed"], 0f); changes.Add("scrollSpeed"); }
if (@params["damping"] != null) { noise.damping = @params["damping"].ToObject<bool>(); changes.Add("damping"); }
if (@params["octaveCount"] != null) { noise.octaveCount = @params["octaveCount"].ToObject<int>(); changes.Add("octaveCount"); }
if (@params["quality"] != null && Enum.TryParse<ParticleSystemNoiseQuality>(@params["quality"].ToString(), true, out var quality)) { noise.quality = quality; changes.Add("quality"); }
EditorUtility.SetDirty(ps);
return new { success = true, message = $"Updated noise: {string.Join(", ", changes)}" };
}
public static object SetRenderer(JObject @params)
{
ParticleSystem ps = ParticleCommon.FindParticleSystem(@params);
if (ps == null) return new { success = false, message = "ParticleSystem not found" };
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>();
if (@params["renderMode"] != null && Enum.TryParse<ParticleSystemRenderMode>(@params["renderMode"].ToString(), true, out var renderMode)) { renderer.renderMode = renderMode; changes.Add("renderMode"); }
if (@params["sortMode"] != null && Enum.TryParse<ParticleSystemSortMode>(@params["sortMode"].ToString(), true, out var sortMode)) { renderer.sortMode = sortMode; changes.Add("sortMode"); }
if (@params["minParticleSize"] != null) { renderer.minParticleSize = @params["minParticleSize"].ToObject<float>(); changes.Add("minParticleSize"); }
if (@params["maxParticleSize"] != null) { renderer.maxParticleSize = @params["maxParticleSize"].ToObject<float>(); changes.Add("maxParticleSize"); }
if (@params["lengthScale"] != null) { renderer.lengthScale = @params["lengthScale"].ToObject<float>(); changes.Add("lengthScale"); }
if (@params["velocityScale"] != null) { renderer.velocityScale = @params["velocityScale"].ToObject<float>(); changes.Add("velocityScale"); }
if (@params["cameraVelocityScale"] != null) { renderer.cameraVelocityScale = @params["cameraVelocityScale"].ToObject<float>(); changes.Add("cameraVelocityScale"); }
if (@params["normalDirection"] != null) { renderer.normalDirection = @params["normalDirection"].ToObject<float>(); changes.Add("normalDirection"); }
if (@params["alignment"] != null && Enum.TryParse<ParticleSystemRenderSpace>(@params["alignment"].ToString(), true, out var alignment)) { renderer.alignment = alignment; changes.Add("alignment"); }
if (@params["pivot"] != null) { renderer.pivot = ManageVfxCommon.ParseVector3(@params["pivot"]); changes.Add("pivot"); }
if (@params["flip"] != null) { renderer.flip = ManageVfxCommon.ParseVector3(@params["flip"]); changes.Add("flip"); }
if (@params["allowRoll"] != null) { renderer.allowRoll = @params["allowRoll"].ToObject<bool>(); changes.Add("allowRoll"); }
if (@params["shadowBias"] != null) { renderer.shadowBias = @params["shadowBias"].ToObject<float>(); changes.Add("shadowBias"); }
RendererHelpers.ApplyCommonRendererProperties(renderer, @params, changes);
if (@params["materialPath"] != null)
{
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={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() };
Material mat = ObjectResolver.Resolve(findInst, typeof(Material)) as Material;
if (mat != null) { renderer.trailMaterial = mat; changes.Add("trailMaterial"); }
}
EditorUtility.SetDirty(renderer);
return new { success = true, message = $"Updated renderer: {string.Join(", ", changes)}" };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a68818a59fac4e2c83ad23433ddc9c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Helpers;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class TrailControl
{
public static object Clear(JObject @params)
{
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
Undo.RecordObject(tr, "Clear Trail");
tr.Clear();
return new { success = true, message = "Trail cleared" };
}
public static object Emit(JObject @params)
{
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);
return new { success = true, message = $"Emitted at ({pos.x}, {pos.y}, {pos.z})" };
#else
return new { success = false, message = "AddPosition requires Unity 2021.1+" };
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edebad99699494d5585418395a2bf518
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class TrailRead
{
public static TrailRenderer FindTrailRenderer(JObject @params)
{
GameObject go = ManageVfxCommon.FindTargetGameObject(@params);
return go?.GetComponent<TrailRenderer>();
}
public static object GetInfo(JObject @params)
{
TrailRenderer tr = FindTrailRenderer(@params);
if (tr == null) return new { success = false, message = "TrailRenderer not found" };
return new
{
success = true,
data = new
{
gameObject = tr.gameObject.name,
time = tr.time,
startWidth = tr.startWidth,
endWidth = tr.endWidth,
minVertexDistance = tr.minVertexDistance,
emitting = tr.emitting,
autodestruct = tr.autodestruct,
positionCount = tr.positionCount,
alignment = tr.alignment.ToString(),
textureMode = tr.textureMode.ToString(),
numCornerVertices = tr.numCornerVertices,
numCapVertices = tr.numCapVertices,
generateLightingData = tr.generateLightingData,
material = tr.sharedMaterial?.name,
shadowCastingMode = tr.shadowCastingMode.ToString(),
receiveShadows = tr.receiveShadows,
lightProbeUsage = tr.lightProbeUsage.ToString(),
reflectionProbeUsage = tr.reflectionProbeUsage.ToString(),
sortingOrder = tr.sortingOrder,
sortingLayerName = tr.sortingLayerName,
renderingLayerMask = tr.renderingLayerMask
}
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2921f0042777b4ebbaec4c79c60908a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace MCPForUnity.Editor.Tools.Vfx
{
internal static class TrailWrite
{
public static object SetTime(JObject @params)
{
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");
tr.time = time;
EditorUtility.SetDirty(tr);
return new { success = true, message = $"Set trail time to {time}s" };
}
public static object SetWidth(JObject @params)
{
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>();
RendererHelpers.ApplyWidthProperties(@params, changes,
v => tr.startWidth = v, v => tr.endWidth = v,
v => tr.widthCurve = v, v => tr.widthMultiplier = v,
ManageVfxCommon.ParseAnimationCurve);
EditorUtility.SetDirty(tr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetColor(JObject @params)
{
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>();
RendererHelpers.ApplyColorProperties(@params, changes,
v => tr.startColor = v, v => tr.endColor = v,
v => tr.colorGradient = v,
ManageVfxCommon.ParseColor, ManageVfxCommon.ParseGradient, fadeEndAlpha: true);
EditorUtility.SetDirty(tr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
public static object SetMaterial(JObject @params)
{
TrailRenderer tr = TrailRead.FindTrailRenderer(@params);
return RendererHelpers.SetRendererMaterial(tr, @params, "Set Trail Material", ManageVfxCommon.FindMaterialByPath);
}
public static object SetProperties(JObject @params)
{
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"); }
RendererHelpers.ApplyLineTrailProperties(@params, changes,
null, null,
v => tr.numCornerVertices = v, v => tr.numCapVertices = v,
v => tr.alignment = v, v => tr.textureMode = v,
v => tr.generateLightingData = v);
RendererHelpers.ApplyCommonRendererProperties(tr, @params, changes);
EditorUtility.SetDirty(tr);
return new { success = true, message = $"Updated: {string.Join(", ", changes)}" };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33ba432240c134206a4f71ab24f0fb3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,568 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
/// <summary>
/// Asset management operations for VFX Graph.
/// Handles creating, assigning, and listing VFX assets.
/// Requires com.unity.visualeffectgraph package and UNITY_VFX_GRAPH symbol.
/// </summary>
internal static class VfxGraphAssets
{
#if !UNITY_VFX_GRAPH
public static object CreateAsset(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object AssignAsset(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object ListTemplates(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object ListAssets(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
#else
private static readonly string[] SupportedVfxGraphVersions = { "12.1" };
/// <summary>
/// Creates a new VFX Graph asset file from a template.
/// </summary>
public static object CreateAsset(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" };
}
string versionError = ValidateVfxGraphVersion();
if (!string.IsNullOrEmpty(versionError))
{
return new { success = false, message = versionError };
}
// 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<VisualEffectAsset>(assetPath) != null)
{
bool overwrite = @params["overwrite"]?.ToObject<bool>() ?? false;
if (!overwrite)
{
return new { success = false, message = $"Asset already exists at {assetPath}. Set overwrite=true to replace." };
}
AssetDatabase.DeleteAsset(assetPath);
}
// Find template asset and copy it
string templatePath = FindTemplate(template);
string templateAssetPath = TryGetAssetPathFromFileSystem(templatePath);
VisualEffectAsset newAsset = null;
if (!string.IsNullOrEmpty(templateAssetPath))
{
// Copy the asset to create a new VFX Graph asset
if (!AssetDatabase.CopyAsset(templateAssetPath, assetPath))
{
return new { success = false, message = $"Failed to copy VFX template from {templateAssetPath}" };
}
AssetDatabase.Refresh();
newAsset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(assetPath);
}
else
{
return new { success = false, message = "VFX template not found. Add a .vfx template asset or install VFX Graph templates." };
}
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
}
};
}
/// <summary>
/// Finds VFX template path by name.
/// </summary>
private static string FindTemplate(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<string>();
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)
{
string searchRoot = basePath;
if (basePath.StartsWith("Assets/"))
{
searchRoot = System.IO.Path.Combine(UnityEngine.Application.dataPath, basePath.Substring("Assets/".Length));
}
if (!System.IO.Directory.Exists(searchRoot))
{
continue;
}
foreach (string pattern in templatePatterns)
{
string[] files = System.IO.Directory.GetFiles(searchRoot, pattern, System.IO.SearchOption.AllDirectories);
if (files.Length > 0)
{
return files[0];
}
}
// Also search by partial match
try
{
string[] allVfxFiles = System.IO.Directory.GetFiles(searchRoot, "*.vfx", System.IO.SearchOption.AllDirectories);
foreach (string file in allVfxFiles)
{
if (System.IO.Path.GetFileNameWithoutExtension(file).ToLower().Contains(templateName.ToLower()))
{
return file;
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to search VFX templates under '{searchRoot}': {ex.Message}");
}
}
// Search in project assets
string[] guids = AssetDatabase.FindAssets("t:VisualEffectAsset " + templateName);
if (guids.Length > 0)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
// Convert asset path (e.g., "Assets/...") to absolute filesystem path
if (!string.IsNullOrEmpty(assetPath) && assetPath.StartsWith("Assets/"))
{
return System.IO.Path.Combine(UnityEngine.Application.dataPath, assetPath.Substring("Assets/".Length));
}
if (!string.IsNullOrEmpty(assetPath) && assetPath.StartsWith("Packages/"))
{
var info = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
if (info != null)
{
string relPath = assetPath.Substring(("Packages/" + info.name + "/").Length);
return System.IO.Path.Combine(info.resolvedPath, relPath);
}
}
return null;
}
return null;
}
/// <summary>
/// Assigns a VFX asset to a VisualEffect component.
/// </summary>
public static object AssignAsset(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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" };
}
// Validate and normalize path
// Reject absolute paths, parent directory traversal, and backslashes
if (assetPath.Contains("\\") || assetPath.Contains("..") || System.IO.Path.IsPathRooted(assetPath))
{
return new { success = false, message = "Invalid assetPath: traversal and absolute paths are not allowed" };
}
if (assetPath.StartsWith("Packages/"))
{
return new { success = false, message = "Invalid assetPath: VFX assets must live under Assets/." };
}
if (!assetPath.StartsWith("Assets/"))
{
assetPath = "Assets/" + assetPath;
}
if (!assetPath.EndsWith(".vfx"))
{
assetPath += ".vfx";
}
// Verify the normalized path doesn't escape the project
string fullPath = System.IO.Path.Combine(UnityEngine.Application.dataPath, assetPath.Substring("Assets/".Length));
string canonicalProjectRoot = System.IO.Path.GetFullPath(UnityEngine.Application.dataPath);
string canonicalAssetPath = System.IO.Path.GetFullPath(fullPath);
if (!canonicalAssetPath.StartsWith(canonicalProjectRoot + System.IO.Path.DirectorySeparatorChar) &&
canonicalAssetPath != canonicalProjectRoot)
{
return new { success = false, message = "Invalid assetPath: would escape project directory" };
}
var asset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(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<VisualEffectAsset>(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
}
};
}
/// <summary>
/// Lists available VFX templates.
/// </summary>
public static object ListTemplates(JObject @params)
{
var templates = new List<object>();
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// 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<string>();
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;
}
string normalizedPath = projectRelativePath.Replace("\\", "/");
if (seenPaths.Add(normalizedPath))
{
templates.Add(new { name = name, path = projectRelativePath, source = isPackage ? "package" : "project" });
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to list VFX templates under '{basePath}': {ex.Message}");
}
}
// Also search project assets
string[] guids = AssetDatabase.FindAssets("t:VisualEffectAsset");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
string normalizedPath = path.Replace("\\", "/");
if (seenPaths.Add(normalizedPath))
{
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
}
};
}
/// <summary>
/// Lists all VFX assets in the project.
/// </summary>
public static object ListAssets(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))
{
if (searchFolder.Contains("\\") || searchFolder.Contains("..") || System.IO.Path.IsPathRooted(searchFolder))
{
return new { success = false, message = "Invalid folder: traversal and absolute paths are not allowed" };
}
if (searchFolder.StartsWith("Packages/"))
{
return new { success = false, message = "Invalid folder: VFX assets must live under Assets/." };
}
if (!searchFolder.StartsWith("Assets/"))
{
searchFolder = "Assets/" + searchFolder;
}
string fullPath = System.IO.Path.Combine(UnityEngine.Application.dataPath, searchFolder.Substring("Assets/".Length));
string canonicalProjectRoot = System.IO.Path.GetFullPath(UnityEngine.Application.dataPath);
string canonicalSearchFolder = System.IO.Path.GetFullPath(fullPath);
if (!canonicalSearchFolder.StartsWith(canonicalProjectRoot + System.IO.Path.DirectorySeparatorChar) &&
canonicalSearchFolder != canonicalProjectRoot)
{
return new { success = false, message = "Invalid folder: would escape project directory" };
}
guids = AssetDatabase.FindAssets(filter, new[] { searchFolder });
}
else
{
guids = AssetDatabase.FindAssets(filter);
}
var assets = new List<object>();
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(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 string ValidateVfxGraphVersion()
{
var info = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.visualeffectgraph");
if (info == null)
{
return "VFX Graph package (com.unity.visualeffectgraph) not installed";
}
if (IsVersionSupported(info.version))
{
return null;
}
string supported = string.Join(", ", SupportedVfxGraphVersions.Select(version => $"{version}.x"));
return $"Unsupported VFX Graph version {info.version}. Supported versions: {supported}.";
}
private static bool IsVersionSupported(string installedVersion)
{
if (string.IsNullOrEmpty(installedVersion))
{
return false;
}
string normalized = installedVersion;
int suffixIndex = normalized.IndexOfAny(new[] { '-', '+' });
if (suffixIndex >= 0)
{
normalized = normalized.Substring(0, suffixIndex);
}
if (!Version.TryParse(normalized, out Version installed))
{
return false;
}
foreach (string supported in SupportedVfxGraphVersions)
{
if (!Version.TryParse(supported, out Version target))
{
continue;
}
if (installed.Major == target.Major && installed.Minor == target.Minor)
{
return true;
}
}
return false;
}
private static string TryGetAssetPathFromFileSystem(string templatePath)
{
if (string.IsNullOrEmpty(templatePath))
{
return null;
}
string normalized = templatePath.Replace("\\", "/");
string assetsRoot = Application.dataPath.Replace("\\", "/");
if (normalized.StartsWith(assetsRoot + "/"))
{
return "Assets/" + normalized.Substring(assetsRoot.Length + 1);
}
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.visualeffectgraph");
if (packageInfo != null)
{
string packageRoot = packageInfo.resolvedPath.Replace("\\", "/");
if (normalized.StartsWith(packageRoot + "/"))
{
return "Packages/" + packageInfo.name + "/" + normalized.Substring(packageRoot.Length + 1);
}
}
return null;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1dfb51f038764a6da23619cac60f299
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
/// <summary>
/// Common utilities for VFX Graph operations.
/// </summary>
internal static class VfxGraphCommon
{
#if UNITY_VFX_GRAPH
/// <summary>
/// Finds a VisualEffect component on the target GameObject.
/// </summary>
public static VisualEffect FindVisualEffect(JObject @params)
{
if (@params == null)
return null;
GameObject go = ManageVfxCommon.FindTargetGameObject(@params);
return go?.GetComponent<VisualEffect>();
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a6dbf78125194cf29b98d658af1039a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
/// <summary>
/// Playback control operations for VFX Graph (VisualEffect component).
/// Requires com.unity.visualeffectgraph package and UNITY_VFX_GRAPH symbol.
/// </summary>
internal static class VfxGraphControl
{
#if !UNITY_VFX_GRAPH
public static object Control(JObject @params, string action)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetPlaybackSpeed(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetSeed(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
#else
public static object Control(JObject @params, string action)
{
VisualEffect vfx = VfxGraphCommon.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;
default:
return new { success = false, message = $"Unknown VFX action: {action}" };
}
return new { success = true, message = $"VFX {action}", isPaused = vfx.pause };
}
public static object SetPlaybackSpeed(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.FindVisualEffect(@params);
if (vfx == null)
{
return new { success = false, message = "VisualEffect not found" };
}
float rate = @params["playRate"]?.ToObject<float>() ?? 1f;
Undo.RecordObject(vfx, "Set VFX Play Rate");
vfx.playRate = rate;
EditorUtility.SetDirty(vfx);
return new { success = true, message = $"Set play rate = {rate}" };
}
public static object SetSeed(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.FindVisualEffect(@params);
if (vfx == null)
{
return new { success = false, message = "VisualEffect not found" };
}
uint seed = @params["seed"]?.ToObject<uint>() ?? 0;
bool resetOnPlay = @params["resetSeedOnPlay"]?.ToObject<bool>() ?? 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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4720d53b13bc14989803670a788a1eaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
/// <summary>
/// Read operations for VFX Graph (VisualEffect component).
/// Requires com.unity.visualeffectgraph package and UNITY_VFX_GRAPH symbol.
/// </summary>
internal static class VfxGraphRead
{
#if !UNITY_VFX_GRAPH
public static object GetInfo(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
#else
public static object GetInfo(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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
}
};
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 419e293a95ea64af5ad6984b1d02b9b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,310 @@
using System;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
/// <summary>
/// Parameter setter operations for VFX Graph (VisualEffect component).
/// Requires com.unity.visualeffectgraph package and UNITY_VFX_GRAPH symbol.
/// </summary>
internal static class VfxGraphWrite
{
#if !UNITY_VFX_GRAPH
public static object SetParameter<T>(JObject @params, Action<object, string, T> setter)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetVector(JObject @params, int dims)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetColor(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetGradient(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetTexture(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetMesh(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SetCurve(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object SendEvent(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
#else
public static object SetParameter<T>(JObject @params, Action<VisualEffect, string, T> setter)
{
VisualEffect vfx = VfxGraphCommon.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" };
}
// Safely deserialize the value
T value;
try
{
value = valueToken.ToObject<T>();
}
catch (JsonException ex)
{
return new { success = false, message = $"Invalid value for {param}: {ex.Message}" };
}
catch (InvalidCastException ex)
{
return new { success = false, message = $"Invalid value type for {param}: {ex.Message}" };
}
Undo.RecordObject(vfx, $"Set VFX {param}");
setter(vfx, param, value);
EditorUtility.SetDirty(vfx);
return new { success = true, message = $"Set {param} = {value}" };
}
public static object SetVector(JObject @params, int dims)
{
VisualEffect vfx = VfxGraphCommon.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" };
}
if (dims != 2 && dims != 3 && dims != 4)
{
return new { success = false, message = $"Unsupported vector dimension: {dims}. Expected 2, 3, or 4." };
}
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}" };
}
public static object SetColor(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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}" };
}
public static object SetGradient(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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}" };
}
public static object SetTexture(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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}" };
}
public static object SetMesh(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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}" };
}
public static object SetCurve(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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}" };
}
public static object SendEvent(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.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)
{
float? sizeValue = @params["size"].Value<float?>();
if (sizeValue.HasValue)
{
attr.SetFloat("size", sizeValue.Value);
}
}
if (@params["lifetime"] != null)
{
float? lifetimeValue = @params["lifetime"].Value<float?>();
if (lifetimeValue.HasValue)
{
attr.SetFloat("lifetime", lifetimeValue.Value);
}
}
vfx.SendEvent(eventName, attr);
return new { success = true, message = $"Sent event '{eventName}'" };
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7516cdde6a4b648c9a2def6c26103cc4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: