121 lines
4.6 KiB
Python
121 lines
4.6 KiB
Python
from typing import Annotated, Any, Literal
|
|
|
|
from fastmcp import Context
|
|
from mcp.types import ToolAnnotations
|
|
|
|
from services.registry import mcp_for_unity_tool
|
|
from services.tools import get_unity_instance_from_context
|
|
from transport.unity_transport import send_with_unity_instance
|
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
|
|
|
# All possible actions grouped by component type
|
|
PARTICLE_ACTIONS = [
|
|
"particle_get_info", "particle_set_main", "particle_set_emission", "particle_set_shape",
|
|
"particle_set_color_over_lifetime", "particle_set_size_over_lifetime",
|
|
"particle_set_velocity_over_lifetime", "particle_set_noise", "particle_set_renderer",
|
|
"particle_enable_module", "particle_play", "particle_stop", "particle_pause",
|
|
"particle_restart", "particle_clear", "particle_add_burst", "particle_clear_bursts"
|
|
]
|
|
|
|
VFX_ACTIONS = [
|
|
# Asset management
|
|
"vfx_create_asset", "vfx_assign_asset", "vfx_list_templates", "vfx_list_assets",
|
|
# Runtime control
|
|
"vfx_get_info", "vfx_set_float", "vfx_set_int", "vfx_set_bool",
|
|
"vfx_set_vector2", "vfx_set_vector3", "vfx_set_vector4", "vfx_set_color",
|
|
"vfx_set_gradient", "vfx_set_texture", "vfx_set_mesh", "vfx_set_curve",
|
|
"vfx_send_event", "vfx_play", "vfx_stop", "vfx_pause", "vfx_reinit",
|
|
"vfx_set_playback_speed", "vfx_set_seed"
|
|
]
|
|
|
|
LINE_ACTIONS = [
|
|
"line_get_info", "line_set_positions", "line_add_position", "line_set_position",
|
|
"line_set_width", "line_set_color", "line_set_material", "line_set_properties",
|
|
"line_clear", "line_create_line", "line_create_circle", "line_create_arc", "line_create_bezier"
|
|
]
|
|
|
|
TRAIL_ACTIONS = [
|
|
"trail_get_info", "trail_set_time", "trail_set_width", "trail_set_color",
|
|
"trail_set_material", "trail_set_properties", "trail_clear", "trail_emit"
|
|
]
|
|
|
|
ALL_ACTIONS = ["ping"] + PARTICLE_ACTIONS + VFX_ACTIONS + LINE_ACTIONS + TRAIL_ACTIONS
|
|
|
|
|
|
@mcp_for_unity_tool(
|
|
description=(
|
|
"Manage Unity VFX components (ParticleSystem, VisualEffect, LineRenderer, TrailRenderer). "
|
|
"Action prefixes: particle_*, vfx_*, line_*, trail_*. "
|
|
"Action-specific parameters go in `properties` (keys match ManageVFX.cs)."
|
|
),
|
|
annotations=ToolAnnotations(
|
|
title="Manage VFX",
|
|
destructiveHint=True,
|
|
),
|
|
)
|
|
async def manage_vfx(
|
|
ctx: Context,
|
|
action: Annotated[str, "Action to perform (prefix: particle_, vfx_, line_, trail_)."],
|
|
target: Annotated[str | None, "Target GameObject (name/path/id)."] = None,
|
|
search_method: Annotated[
|
|
Literal["by_id", "by_name", "by_path", "by_tag", "by_layer"] | None,
|
|
"How to find the target GameObject.",
|
|
] = None,
|
|
properties: Annotated[
|
|
dict[str, Any] | str | None,
|
|
"Action-specific parameters (dict or JSON string).",
|
|
] = None,
|
|
) -> dict[str, Any]:
|
|
"""Unified VFX management tool."""
|
|
|
|
# Normalize action to lowercase to match Unity-side behavior
|
|
action_normalized = action.lower()
|
|
|
|
# Validate action against known actions using normalized value
|
|
if action_normalized not in ALL_ACTIONS:
|
|
# Provide helpful error with closest matches by prefix
|
|
prefix = action_normalized.split(
|
|
"_")[0] + "_" if "_" in action_normalized else ""
|
|
available_by_prefix = {
|
|
"particle_": PARTICLE_ACTIONS,
|
|
"vfx_": VFX_ACTIONS,
|
|
"line_": LINE_ACTIONS,
|
|
"trail_": TRAIL_ACTIONS,
|
|
}
|
|
suggestions = available_by_prefix.get(prefix, [])
|
|
if suggestions:
|
|
return {
|
|
"success": False,
|
|
"message": f"Unknown action '{action}'. Available {prefix}* actions: {', '.join(suggestions)}",
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"message": (
|
|
f"Unknown action '{action}'. Use prefixes: "
|
|
"particle_*, vfx_*, line_*, trail_*. Run with action='ping' to test connection."
|
|
),
|
|
}
|
|
|
|
unity_instance = get_unity_instance_from_context(ctx)
|
|
|
|
params_dict: dict[str, Any] = {"action": action_normalized}
|
|
if properties is not None:
|
|
params_dict["properties"] = properties
|
|
if target is not None:
|
|
params_dict["target"] = target
|
|
if search_method is not None:
|
|
params_dict["searchMethod"] = search_method
|
|
|
|
params_dict = {k: v for k, v in params_dict.items() if v is not None}
|
|
|
|
# Send to Unity
|
|
result = await send_with_unity_instance(
|
|
async_send_command_with_retry,
|
|
unity_instance,
|
|
"manage_vfx",
|
|
params_dict,
|
|
)
|
|
|
|
return result if isinstance(result, dict) else {"success": False, "message": str(result)}
|