511 lines
13 KiB
Python
511 lines
13 KiB
Python
"""GameObject CLI commands."""
|
|
|
|
import sys
|
|
import json
|
|
import click
|
|
from typing import Optional, Tuple, Any
|
|
|
|
from cli.utils.config import get_config
|
|
from cli.utils.output import format_output, print_error, print_success, print_warning
|
|
from cli.utils.connection import run_command, UnityConnectionError
|
|
|
|
|
|
@click.group()
|
|
def gameobject():
|
|
"""GameObject operations - create, find, modify, delete GameObjects."""
|
|
pass
|
|
|
|
|
|
@gameobject.command("find")
|
|
@click.argument("search_term")
|
|
@click.option(
|
|
"--method", "-m",
|
|
type=click.Choice(["by_name", "by_tag", "by_layer",
|
|
"by_component", "by_path", "by_id"]),
|
|
default="by_name",
|
|
help="Search method."
|
|
)
|
|
@click.option(
|
|
"--include-inactive", "-i",
|
|
is_flag=True,
|
|
help="Include inactive GameObjects."
|
|
)
|
|
@click.option(
|
|
"--limit", "-l",
|
|
default=50,
|
|
type=int,
|
|
help="Maximum results to return."
|
|
)
|
|
@click.option(
|
|
"--cursor", "-c",
|
|
default=0,
|
|
type=int,
|
|
help="Pagination cursor (offset)."
|
|
)
|
|
def find(search_term: str, method: str, include_inactive: bool, limit: int, cursor: int):
|
|
"""Find GameObjects by search criteria.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject find "Player"
|
|
unity-mcp gameobject find "Enemy" --method by_tag
|
|
unity-mcp gameobject find "-81840" --method by_id
|
|
unity-mcp gameobject find "Rigidbody" --method by_component
|
|
unity-mcp gameobject find "/Canvas/Panel" --method by_path
|
|
"""
|
|
config = get_config()
|
|
|
|
try:
|
|
result = run_command("find_gameobjects", {
|
|
"searchMethod": method,
|
|
"searchTerm": search_term,
|
|
"includeInactive": include_inactive,
|
|
"pageSize": limit,
|
|
"cursor": cursor,
|
|
}, config)
|
|
click.echo(format_output(result, config.format))
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|
|
|
|
|
|
@gameobject.command("create")
|
|
@click.argument("name")
|
|
@click.option(
|
|
"--primitive", "-p",
|
|
type=click.Choice(["Cube", "Sphere", "Cylinder",
|
|
"Plane", "Capsule", "Quad"]),
|
|
help="Create a primitive type."
|
|
)
|
|
@click.option(
|
|
"--position", "-pos",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="Position as X Y Z."
|
|
)
|
|
@click.option(
|
|
"--rotation", "-rot",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="Rotation as X Y Z (euler angles)."
|
|
)
|
|
@click.option(
|
|
"--scale", "-s",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="Scale as X Y Z."
|
|
)
|
|
@click.option(
|
|
"--parent",
|
|
default=None,
|
|
help="Parent GameObject name or path."
|
|
)
|
|
@click.option(
|
|
"--tag", "-t",
|
|
default=None,
|
|
help="Tag to assign."
|
|
)
|
|
@click.option(
|
|
"--layer",
|
|
default=None,
|
|
help="Layer to assign."
|
|
)
|
|
@click.option(
|
|
"--components",
|
|
default=None,
|
|
help="Comma-separated list of components to add."
|
|
)
|
|
@click.option(
|
|
"--save-prefab",
|
|
is_flag=True,
|
|
help="Save as prefab after creation."
|
|
)
|
|
@click.option(
|
|
"--prefab-path",
|
|
default=None,
|
|
help="Path for prefab (e.g., Assets/Prefabs/MyPrefab.prefab)."
|
|
)
|
|
def create(
|
|
name: str,
|
|
primitive: Optional[str],
|
|
position: Optional[Tuple[float, float, float]],
|
|
rotation: Optional[Tuple[float, float, float]],
|
|
scale: Optional[Tuple[float, float, float]],
|
|
parent: Optional[str],
|
|
tag: Optional[str],
|
|
layer: Optional[str],
|
|
components: Optional[str],
|
|
save_prefab: bool,
|
|
prefab_path: Optional[str],
|
|
):
|
|
"""Create a new GameObject.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject create "MyCube" --primitive Cube
|
|
unity-mcp gameobject create "Player" --position 0 1 0
|
|
unity-mcp gameobject create "Enemy" --primitive Sphere --tag Enemy
|
|
unity-mcp gameobject create "Child" --parent "ParentObject"
|
|
unity-mcp gameobject create "Item" --components "Rigidbody,BoxCollider"
|
|
"""
|
|
config = get_config()
|
|
|
|
params: dict[str, Any] = {
|
|
"action": "create",
|
|
"name": name,
|
|
}
|
|
|
|
if primitive:
|
|
params["primitiveType"] = primitive
|
|
if position:
|
|
params["position"] = list(position)
|
|
if rotation:
|
|
params["rotation"] = list(rotation)
|
|
if scale:
|
|
params["scale"] = list(scale)
|
|
if parent:
|
|
params["parent"] = parent
|
|
if tag:
|
|
params["tag"] = tag
|
|
if layer:
|
|
params["layer"] = layer
|
|
if save_prefab:
|
|
params["saveAsPrefab"] = True
|
|
if prefab_path:
|
|
params["prefabPath"] = prefab_path
|
|
|
|
try:
|
|
result = run_command("manage_gameobject", params, config)
|
|
|
|
# Add components separately since componentsToAdd doesn't work
|
|
if components and (result.get("success") or result.get("data") or result.get("result")):
|
|
component_list = [c.strip() for c in components.split(",")]
|
|
failed_components = []
|
|
for component in component_list:
|
|
try:
|
|
run_command("manage_components", {
|
|
"action": "add",
|
|
"target": name,
|
|
"componentType": component,
|
|
}, config)
|
|
except UnityConnectionError:
|
|
failed_components.append(component)
|
|
if failed_components:
|
|
print_warning(
|
|
f"Failed to add components: {', '.join(failed_components)}")
|
|
|
|
click.echo(format_output(result, config.format))
|
|
if result.get("success") or result.get("result"):
|
|
print_success(f"Created GameObject '{name}'")
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|
|
|
|
|
|
@gameobject.command("modify")
|
|
@click.argument("target")
|
|
@click.option(
|
|
"--name", "-n",
|
|
default=None,
|
|
help="New name for the GameObject."
|
|
)
|
|
@click.option(
|
|
"--position", "-pos",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="New position as X Y Z."
|
|
)
|
|
@click.option(
|
|
"--rotation", "-rot",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="New rotation as X Y Z (euler angles)."
|
|
)
|
|
@click.option(
|
|
"--scale", "-s",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="New scale as X Y Z."
|
|
)
|
|
@click.option(
|
|
"--parent",
|
|
default=None,
|
|
help="New parent GameObject."
|
|
)
|
|
@click.option(
|
|
"--tag", "-t",
|
|
default=None,
|
|
help="New tag."
|
|
)
|
|
@click.option(
|
|
"--layer",
|
|
default=None,
|
|
help="New layer."
|
|
)
|
|
@click.option(
|
|
"--active/--inactive",
|
|
default=None,
|
|
help="Set active state."
|
|
)
|
|
@click.option(
|
|
"--add-components",
|
|
default=None,
|
|
help="Comma-separated list of components to add."
|
|
)
|
|
@click.option(
|
|
"--remove-components",
|
|
default=None,
|
|
help="Comma-separated list of components to remove."
|
|
)
|
|
@click.option(
|
|
"--search-method",
|
|
type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
|
|
default=None,
|
|
help="How to find the target GameObject."
|
|
)
|
|
def modify(
|
|
target: str,
|
|
name: Optional[str],
|
|
position: Optional[Tuple[float, float, float]],
|
|
rotation: Optional[Tuple[float, float, float]],
|
|
scale: Optional[Tuple[float, float, float]],
|
|
parent: Optional[str],
|
|
tag: Optional[str],
|
|
layer: Optional[str],
|
|
active: Optional[bool],
|
|
add_components: Optional[str],
|
|
remove_components: Optional[str],
|
|
search_method: Optional[str],
|
|
):
|
|
"""Modify an existing GameObject.
|
|
|
|
TARGET can be a name, path, instance ID, or tag depending on --search-method.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject modify "Player" --position 0 5 0
|
|
unity-mcp gameobject modify "Enemy" --name "Boss" --tag "Boss"
|
|
unity-mcp gameobject modify "-81840" --search-method by_id --active
|
|
unity-mcp gameobject modify "/Canvas/Panel" --search-method by_path --inactive
|
|
unity-mcp gameobject modify "Cube" --add-components "Rigidbody,BoxCollider"
|
|
"""
|
|
config = get_config()
|
|
|
|
params: dict[str, Any] = {
|
|
"action": "modify",
|
|
"target": target,
|
|
}
|
|
|
|
if name:
|
|
params["name"] = name
|
|
if position:
|
|
params["position"] = list(position)
|
|
if rotation:
|
|
params["rotation"] = list(rotation)
|
|
if scale:
|
|
params["scale"] = list(scale)
|
|
if parent:
|
|
params["parent"] = parent
|
|
if tag:
|
|
params["tag"] = tag
|
|
if layer:
|
|
params["layer"] = layer
|
|
if active is not None:
|
|
params["setActive"] = active
|
|
if add_components:
|
|
params["componentsToAdd"] = [c.strip()
|
|
for c in add_components.split(",")]
|
|
if remove_components:
|
|
params["componentsToRemove"] = [c.strip()
|
|
for c in remove_components.split(",")]
|
|
if search_method:
|
|
params["searchMethod"] = search_method
|
|
|
|
try:
|
|
result = run_command("manage_gameobject", params, config)
|
|
click.echo(format_output(result, config.format))
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|
|
|
|
|
|
@gameobject.command("delete")
|
|
@click.argument("target")
|
|
@click.option(
|
|
"--search-method",
|
|
type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
|
|
default=None,
|
|
help="How to find the target GameObject."
|
|
)
|
|
@click.option(
|
|
"--force", "-f",
|
|
is_flag=True,
|
|
help="Skip confirmation prompt."
|
|
)
|
|
def delete(target: str, search_method: Optional[str], force: bool):
|
|
"""Delete a GameObject.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject delete "OldObject"
|
|
unity-mcp gameobject delete "-81840" --search-method by_id
|
|
unity-mcp gameobject delete "TempObjects" --search-method by_tag --force
|
|
"""
|
|
config = get_config()
|
|
|
|
if not force:
|
|
click.confirm(f"Delete GameObject '{target}'?", abort=True)
|
|
|
|
params = {
|
|
"action": "delete",
|
|
"target": target,
|
|
}
|
|
|
|
if search_method:
|
|
params["searchMethod"] = search_method
|
|
|
|
try:
|
|
result = run_command("manage_gameobject", params, config)
|
|
click.echo(format_output(result, config.format))
|
|
if result.get("success"):
|
|
print_success(f"Deleted GameObject '{target}'")
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|
|
|
|
|
|
@gameobject.command("duplicate")
|
|
@click.argument("target")
|
|
@click.option(
|
|
"--name", "-n",
|
|
default=None,
|
|
help="Name for the duplicate (default: OriginalName_Copy)."
|
|
)
|
|
@click.option(
|
|
"--offset",
|
|
nargs=3,
|
|
type=float,
|
|
default=None,
|
|
help="Position offset from original as X Y Z."
|
|
)
|
|
@click.option(
|
|
"--search-method",
|
|
type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
|
|
default=None,
|
|
help="How to find the target GameObject."
|
|
)
|
|
def duplicate(
|
|
target: str,
|
|
name: Optional[str],
|
|
offset: Optional[Tuple[float, float, float]],
|
|
search_method: Optional[str],
|
|
):
|
|
"""Duplicate a GameObject.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject duplicate "Player"
|
|
unity-mcp gameobject duplicate "Enemy" --name "Enemy2" --offset 5 0 0
|
|
unity-mcp gameobject duplicate "-81840" --search-method by_id
|
|
"""
|
|
config = get_config()
|
|
|
|
params: dict[str, Any] = {
|
|
"action": "duplicate",
|
|
"target": target,
|
|
}
|
|
|
|
if name:
|
|
params["new_name"] = name
|
|
if offset:
|
|
params["offset"] = list(offset)
|
|
if search_method:
|
|
params["searchMethod"] = search_method
|
|
|
|
try:
|
|
result = run_command("manage_gameobject", params, config)
|
|
click.echo(format_output(result, config.format))
|
|
if result.get("success"):
|
|
print_success(f"Duplicated GameObject '{target}'")
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|
|
|
|
|
|
@gameobject.command("move")
|
|
@click.argument("target")
|
|
@click.option(
|
|
"--reference", "-r",
|
|
required=True,
|
|
help="Reference object for relative movement."
|
|
)
|
|
@click.option(
|
|
"--direction", "-d",
|
|
type=click.Choice(["left", "right", "up", "down", "forward",
|
|
"back", "front", "backward", "behind"]),
|
|
required=True,
|
|
help="Direction to move."
|
|
)
|
|
@click.option(
|
|
"--distance",
|
|
type=float,
|
|
default=1.0,
|
|
help="Distance to move (default: 1.0)."
|
|
)
|
|
@click.option(
|
|
"--local",
|
|
is_flag=True,
|
|
help="Use reference object's local space instead of world space."
|
|
)
|
|
@click.option(
|
|
"--search-method",
|
|
type=click.Choice(["by_name", "by_path", "by_tag", "by_id"]),
|
|
default=None,
|
|
help="How to find the target GameObject."
|
|
)
|
|
def move(
|
|
target: str,
|
|
reference: str,
|
|
direction: str,
|
|
distance: float,
|
|
local: bool,
|
|
search_method: Optional[str],
|
|
):
|
|
"""Move a GameObject relative to another object.
|
|
|
|
\b
|
|
Examples:
|
|
unity-mcp gameobject move "Chair" --reference "Table" --direction right --distance 2
|
|
unity-mcp gameobject move "Light" --reference "Player" --direction up --distance 3
|
|
unity-mcp gameobject move "NPC" --reference "Player" --direction forward --local
|
|
"""
|
|
config = get_config()
|
|
|
|
params: dict[str, Any] = {
|
|
"action": "move_relative",
|
|
"target": target,
|
|
"reference_object": reference,
|
|
"direction": direction,
|
|
"distance": distance,
|
|
"world_space": not local,
|
|
}
|
|
|
|
if search_method:
|
|
params["searchMethod"] = search_method
|
|
|
|
try:
|
|
result = run_command("manage_gameobject", params, config)
|
|
click.echo(format_output(result, config.format))
|
|
if result.get("success"):
|
|
print_success(
|
|
f"Moved '{target}' {direction} of '{reference}' by {distance} units")
|
|
except UnityConnectionError as e:
|
|
print_error(str(e))
|
|
sys.exit(1)
|