569 lines
22 KiB
C#
569 lines
22 KiB
C#
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
|
|
}
|
|
}
|