Skip to content

Commit 2e3e65e

Browse files
authored
Merge pull request #1005 from Sibirius/feature/manage-gameobject-is-static
feat(manage_gameobject): add is_static parameter to modify action
2 parents c046f2e + 632488c commit 2e3e65e

4 files changed

Lines changed: 161 additions & 0 deletions

File tree

MCPForUnity/Editor/Tools/GameObjects/GameObjectModify.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,19 @@ internal static object Handle(JObject @params, JToken targetToken, string search
151151
}
152152
}
153153

154+
bool? isStatic = @params["isStatic"]?.ToObject<bool?>();
155+
if (isStatic.HasValue)
156+
{
157+
var desiredFlags = isStatic.Value ? (StaticEditorFlags)~0 : 0;
158+
var currentFlags = GameObjectUtility.GetStaticEditorFlags(targetGo);
159+
160+
if (currentFlags != desiredFlags)
161+
{
162+
GameObjectUtility.SetStaticEditorFlags(targetGo, desiredFlags);
163+
modified = true;
164+
}
165+
}
166+
154167
Vector3? position = VectorParsing.ParseVector3(@params["position"]);
155168
Vector3? rotation = VectorParsing.ParseVector3(@params["rotation"]);
156169
Vector3? scale = VectorParsing.ParseVector3(@params["scale"]);

Server/src/cli/commands/gameobject.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ def create(
246246
default=None,
247247
help="Set active state."
248248
)
249+
@click.option(
250+
"--static/--no-static",
251+
default=None,
252+
help="Set static flag (all StaticEditorFlags on/off)."
253+
)
249254
@click.option(
250255
"--add-components",
251256
default=None,
@@ -273,6 +278,7 @@ def modify(
273278
tag: Optional[str],
274279
layer: Optional[str],
275280
active: Optional[bool],
281+
static: Optional[bool],
276282
add_components: Optional[str],
277283
remove_components: Optional[str],
278284
search_method: Optional[str],
@@ -288,6 +294,7 @@ def modify(
288294
unity-mcp gameobject modify "-81840" --search-method by_id --active
289295
unity-mcp gameobject modify "/Canvas/Panel" --search-method by_path --inactive
290296
unity-mcp gameobject modify "Cube" --add-components "Rigidbody,BoxCollider"
297+
unity-mcp gameobject modify "Ground" --static
291298
"""
292299
config = get_config()
293300

@@ -312,6 +319,8 @@ def modify(
312319
params["layer"] = layer
313320
if active is not None:
314321
params["setActive"] = active
322+
if static is not None:
323+
params["isStatic"] = static
315324
if add_components:
316325
params["componentsToAdd"] = [c.strip() for c in add_components.split(",")]
317326
if remove_components:

Server/src/services/tools/manage_gameobject.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ async def manage_gameobject(
8787
set_active: Annotated[bool | str,
8888
"If True, sets the GameObject active (accepts true/false or 'true'/'false')"] | None = None,
8989
layer: Annotated[str, "Layer name"] | None = None,
90+
is_static: Annotated[bool | str,
91+
"Set the GameObject's static flag. true = all StaticEditorFlags, false = none (accepts true/false or 'true'/'false')"] | None = None,
9092
components_to_remove: Annotated[list[str] | str,
9193
"List of component names to remove"] | None = None,
9294
component_properties: Annotated[dict[str, dict[str, Any]] | str,
@@ -146,6 +148,7 @@ async def manage_gameobject(
146148
# --- Normalize boolean parameters ---
147149
save_as_prefab = coerce_bool(save_as_prefab)
148150
set_active = coerce_bool(set_active)
151+
is_static = coerce_bool(is_static)
149152
world_space = coerce_bool(world_space, default=True)
150153

151154
# --- Normalize component_properties with detailed error handling ---
@@ -182,6 +185,7 @@ async def manage_gameobject(
182185
"prefabFolder": prefab_folder,
183186
"setActive": set_active,
184187
"layer": layer,
188+
"isStatic": is_static,
185189
"componentsToRemove": components_to_remove,
186190
"componentProperties": component_properties,
187191
# Parameters for 'duplicate'
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import pytest
2+
3+
from .test_helpers import DummyContext
4+
import services.tools.manage_gameobject as manage_go_mod
5+
6+
7+
@pytest.mark.asyncio
8+
async def test_manage_gameobject_is_static_true(monkeypatch):
9+
"""Test that is_static=True is passed as isStatic in params."""
10+
captured = {}
11+
12+
async def fake_send(cmd, params, **kwargs):
13+
captured["params"] = params
14+
return {"success": True, "data": {}}
15+
16+
monkeypatch.setattr(
17+
manage_go_mod,
18+
"async_send_command_with_retry",
19+
fake_send,
20+
)
21+
22+
resp = await manage_go_mod.manage_gameobject(
23+
ctx=DummyContext(),
24+
action="modify",
25+
target="Ground",
26+
is_static=True,
27+
)
28+
29+
assert resp.get("success") is True
30+
assert captured["params"]["action"] == "modify"
31+
assert captured["params"]["target"] == "Ground"
32+
assert captured["params"]["isStatic"] is True
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_manage_gameobject_is_static_false(monkeypatch):
37+
"""Test that is_static=False is passed as isStatic in params."""
38+
captured = {}
39+
40+
async def fake_send(cmd, params, **kwargs):
41+
captured["params"] = params
42+
return {"success": True, "data": {}}
43+
44+
monkeypatch.setattr(
45+
manage_go_mod,
46+
"async_send_command_with_retry",
47+
fake_send,
48+
)
49+
50+
resp = await manage_go_mod.manage_gameobject(
51+
ctx=DummyContext(),
52+
action="modify",
53+
target="Ground",
54+
is_static=False,
55+
)
56+
57+
assert resp.get("success") is True
58+
assert captured["params"]["isStatic"] is False
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_manage_gameobject_is_static_string_coercion(monkeypatch):
63+
"""Test that string 'true' is coerced to bool for is_static."""
64+
captured = {}
65+
66+
async def fake_send(cmd, params, **kwargs):
67+
captured["params"] = params
68+
return {"success": True, "data": {}}
69+
70+
monkeypatch.setattr(
71+
manage_go_mod,
72+
"async_send_command_with_retry",
73+
fake_send,
74+
)
75+
76+
resp = await manage_go_mod.manage_gameobject(
77+
ctx=DummyContext(),
78+
action="modify",
79+
target="Ground",
80+
is_static="true",
81+
)
82+
83+
assert resp.get("success") is True
84+
assert captured["params"]["isStatic"] is True
85+
86+
87+
@pytest.mark.asyncio
88+
async def test_manage_gameobject_is_static_string_false_coercion(monkeypatch):
89+
"""Test that string 'false' is coerced to bool for is_static."""
90+
captured = {}
91+
92+
async def fake_send(cmd, params, **kwargs):
93+
captured["params"] = params
94+
return {"success": True, "data": {}}
95+
96+
monkeypatch.setattr(
97+
manage_go_mod,
98+
"async_send_command_with_retry",
99+
fake_send,
100+
)
101+
102+
resp = await manage_go_mod.manage_gameobject(
103+
ctx=DummyContext(),
104+
action="modify",
105+
target="Ground",
106+
is_static="false",
107+
)
108+
109+
assert resp.get("success") is True
110+
assert captured["params"]["isStatic"] is False
111+
112+
113+
@pytest.mark.asyncio
114+
async def test_manage_gameobject_is_static_omitted(monkeypatch):
115+
"""Test that omitting is_static does not include isStatic in params."""
116+
captured = {}
117+
118+
async def fake_send(cmd, params, **kwargs):
119+
captured["params"] = params
120+
return {"success": True, "data": {}}
121+
122+
monkeypatch.setattr(
123+
manage_go_mod,
124+
"async_send_command_with_retry",
125+
fake_send,
126+
)
127+
128+
resp = await manage_go_mod.manage_gameobject(
129+
ctx=DummyContext(),
130+
action="modify",
131+
target="Ground",
132+
)
133+
134+
assert resp.get("success") is True
135+
assert "isStatic" not in captured["params"]

0 commit comments

Comments
 (0)