Skip to content

Conversation

@cyanxwh
Copy link

@cyanxwh cyanxwh commented Jan 17, 2026

Summary

Fixes #547

When a prefab is opened in Unity's Prefab Stage (isolation editing mode), find_gameobjects and related tools now correctly find GameObjects within the prefab.

Changes

  • GameObjectLookup.cs: Modified GetAllSceneObjects() to check for Prefab Stage first using PrefabStageUtility.GetCurrentPrefabStage(). When in prefab editing mode, returns objects from the prefab contents root instead of the main scene.

  • ManageGameObjectCommon.cs: Updated SearchByPath() to handle Prefab Stage - uses prefabContentsRoot.transform.Find() instead of GameObject.Find() which doesn't work in Prefab Stage.

  • ManageScene.cs: Updated GetHierarchy() to return the prefab hierarchy when in Prefab Stage mode.

Testing

  • Tested with NetworkRig prefab open in Prefab Stage
  • Verified find_gameobjects returns correct results
  • Verified path-based searches work correctly
  • Verified scene hierarchy shows prefab contents

🤖 Generated with Claude Code

Summary by Sourcery

Add support for Unity Prefab Stage when querying and managing GameObjects and scene hierarchy.

Bug Fixes:

  • Ensure path-based GameObject lookups work correctly while editing prefabs in Prefab Stage.
  • Fix scene hierarchy retrieval so it returns the prefab context when a Prefab Stage is active.

Enhancements:

  • Support path-based searches for inactive GameObjects by manually traversing scene objects instead of relying solely on GameObject.Find.
  • Centralize scene object enumeration through GameObjectLookup.GetAllSceneObjects to keep lookup behavior consistent across tools.

Summary by CodeRabbit

  • Bug Fixes
    • Improved game object lookup to work correctly when editing prefabs in isolation
    • Enhanced search capabilities to reliably find inactive game objects in scenes
    • Better detection of active scene context during prefab editing workflows

✏️ Tip: You can customize this high-level summary in your review settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 17, 2026

Reviewer's Guide

Adds Prefab Stage awareness to GameObject lookup, path-based search, and hierarchy retrieval so tools like find_gameobjects work correctly when editing prefabs in isolation, while centralizing scene object enumeration logic in GameObjectLookup.

Sequence diagram for by_path search in Prefab Stage

sequenceDiagram
    actor User
    participant ManageGameObjectCommon
    participant GameObjectLookup
    participant PrefabStageUtility
    participant PrefabStage

    User->>ManageGameObjectCommon: FindObjectsInternal(by_path, searchTerm, searchInactive, null)
    ManageGameObjectCommon->>GameObjectLookup: GetAllSceneObjects(searchInactive)
    GameObjectLookup->>PrefabStageUtility: GetCurrentPrefabStage()
    PrefabStageUtility-->>GameObjectLookup: PrefabStage
    GameObjectLookup->>PrefabStage: access prefabContentsRoot
    loop each descendant GameObject
        GameObjectLookup-->>ManageGameObjectCommon: GameObject
    end
    loop each GameObject
        ManageGameObjectCommon->>ManageGameObjectCommon: GetGameObjectPath(go)
        ManageGameObjectCommon->>ManageGameObjectCommon: compare path
        alt path matches
            ManageGameObjectCommon->>ManageGameObjectCommon: add to results
        end
    end
    ManageGameObjectCommon-->>User: List GameObject results
Loading

Class diagram for updated GameObject lookup and scene utilities

classDiagram
    class GameObjectLookup {
        +IEnumerable~int~ SearchByPath(path, includeInactive)
        +IEnumerable~GameObject~ GetAllSceneObjects(includeInactive)
        +IEnumerable~GameObject~ GetObjectAndDescendants(root, includeInactive)
        +string GetGameObjectPath(go)
    }

    class ManageGameObjectCommon {
        +List~GameObject~ FindObjectsInternal(searchMode, searchTerm, searchInactive, rootSearchObject)
        +IEnumerable~GameObject~ GetAllSceneObjects(includeInactive)
    }

    class ManageScene {
        +object GetSceneHierarchyPaged(cmd)
    }

    class PrefabStageUtility {
        +PrefabStage GetCurrentPrefabStage()
    }

    class PrefabStage {
        +GameObject prefabContentsRoot
        +Scene scene
    }

    class SceneManager {
        +Scene GetActiveScene()
    }

    class EditorSceneManager {
        +Scene GetActiveScene()
    }

    class Scene {
        +bool IsValid()
        +bool isLoaded
        +string name
        +GameObject[] GetRootGameObjects()
    }

    class GameObject {
        +int GetInstanceID()
        +Transform transform
    }

    class Transform {
        +Transform Find(path)
        +Transform[] GetComponentsInChildren(includeInactive)
        +GameObject gameObject
    }

    ManageGameObjectCommon ..> GameObjectLookup : uses
    ManageGameObjectCommon ..> SceneManager : uses
    GameObjectLookup ..> SceneManager : uses
    GameObjectLookup ..> PrefabStageUtility : uses
    ManageScene ..> PrefabStageUtility : uses
    ManageScene ..> EditorSceneManager : uses
    PrefabStageUtility ..> PrefabStage : returns
    PrefabStage ..> Scene : owns
    SceneManager ..> Scene : returns
    EditorSceneManager ..> Scene : returns
    GameObject ..> Transform : has
    Transform ..> GameObject : returns
Loading

File-Level Changes

Change Details Files
Make SearchByPath and GetAllSceneObjects prefab‑stage aware and support inactive object lookup without relying on GameObject.Find.
  • In SearchByPath, check PrefabStageUtility.GetCurrentPrefabStage() and, when present, iterate GetAllSceneObjects to match objects by computed hierarchy path instead of using GameObject.Find.
  • For normal scene mode, implement manual path search via GetAllSceneObjects when includeInactive=true, and only use GameObject.Find when includeInactive=false.
  • In GetAllSceneObjects, detect an active Prefab Stage and, when present, traverse prefabContentsRoot and its descendants; otherwise fall back to the active SceneManager scene root objects.
MCPForUnity/Editor/Helpers/GameObjectLookup.cs
Unify object search utilities to use centralized scene enumeration and make path-based searches work in Prefab Stage contexts.
  • In FindObjectsInternal case 'by_path', when no rootSearchObject is provided, search all scene objects (via GetAllSceneObjects) by computed path instead of calling GameObject.Find, so it works in Prefab Stage and supports inactive objects.
  • In the later fallback search (when by_name/by_component fail), replace GameObject.Find with a path search over all scene objects using GameObjectLookup.GetGameObjectPath for consistency and Prefab Stage support.
  • Replace the local GetAllSceneObjects implementation with a delegate call to GameObjectLookup.GetAllSceneObjects to avoid duplication and ensure consistent prefab/scene handling.
MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
Make hierarchy retrieval return the Prefab Stage scene when editing a prefab instead of the main active scene.
  • In GetSceneHierarchyPaged, call PrefabStageUtility.GetCurrentPrefabStage() and, if non-null, use prefabStage.scene as the activeScene and log that Prefab Stage is being used.
  • Fall back to EditorSceneManager.GetActiveScene() and existing logging when not in Prefab Stage, preserving previous behavior for normal scenes.
MCPForUnity/Editor/Tools/ManageScene.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#547 Update GetAllSceneObjects to correctly return GameObjects from the Prefab Stage (using the prefab editing scene/prefabContentsRoot) instead of the main active scene when a prefab is being edited.
#547 Update path-based GameObject search (including SearchByPath and find_gameobjects-related code) so that it works in Prefab Stage instead of relying on GameObject.Find, and respects includeInactive where possible.
#547 Ensure scene hierarchy retrieval used by editor tools returns the prefab hierarchy when in Prefab Stage, not the main scene hierarchy.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

The changes add Prefab Stage support to the GameObject lookup system, enabling the find_gameobjects tool and related utilities to locate objects when editing prefabs in isolation mode. Modifications include Prefab Stage detection, path-based search updates, and scene object enumeration for both standard and prefab editing contexts.

Changes

Cohort / File(s) Summary
Prefab Stage Detection & Path-Based Lookup
MCPForUnity/Editor/Helpers/GameObjectLookup.cs
Adds Prefab Stage awareness to GetAllSceneObjects; when active, iterates prefabContentsRoot instead of scene root objects. Enhances SearchByPath to handle inactive objects via manual enumeration and supports path suffix matching ("/" + path) for both prefab and standard contexts. Imports UnityEditor.SceneManagement for PrefabStageUtility access.
Scene Object Retrieval Centralization
MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs
Refactors GetAllSceneObjects to delegate to GameObjectLookup.GetAllSceneObjects instead of inline logic. Reworks by_path and by_id_or_name_or_path handlers to perform manual scene object enumeration with path/suffix matching when root object is absent or lookup by ID fails.
Active Scene Resolution for Prefab Stage
MCPForUnity/Editor/Tools/ManageScene.cs
Modifies get_hierarchy logic to prioritize Prefab Stage: if active, retrieves scene from prefabStage.scene; otherwise falls back to EditorSceneManager.GetActiveScene. Adds debug logging for Prefab Stage detection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 In Prefab Stage we hop with glee,
No GameObjects lost from our decree,
Isolation mode? No problem now!
We search and find them—take a bow! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding Prefab Stage support for GameObject lookup, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #547: added Prefab Stage checks to GetAllSceneObjects, updated path-based searches to use prefabContentsRoot when in Prefab Stage, and modified GetHierarchy to return prefab hierarchy.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing Prefab Stage support across GameObjectLookup, ManageGameObjectCommon, and ManageScene as required by issue #547.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The path-matching logic (GetGameObjectPath + ==/EndsWith) is now duplicated in multiple places (e.g. SearchByPath and ManageGameObjectCommon); consider extracting a shared helper (e.g. MatchesPath(GameObject, string)) to keep the behavior consistent and easier to maintain.
  • In ManageGameObjectCommon's by_path branch (no rootSearchObject), you're now always scanning all scene objects instead of using GameObject.Find in normal scene mode, which may be a significant behavior and performance change; consider gating the manual scan on Prefab Stage only and falling back to GameObject.Find otherwise, as you did in GameObjectLookup.SearchByPath.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The path-matching logic (`GetGameObjectPath` + `==`/`EndsWith`) is now duplicated in multiple places (e.g. `SearchByPath` and `ManageGameObjectCommon`); consider extracting a shared helper (e.g. `MatchesPath(GameObject, string)`) to keep the behavior consistent and easier to maintain.
- In `ManageGameObjectCommon`'s `by_path` branch (no `rootSearchObject`), you're now always scanning all scene objects instead of using `GameObject.Find` in normal scene mode, which may be a significant behavior and performance change; consider gating the manual scan on Prefab Stage only and falling back to `GameObject.Find` otherwise, as you did in `GameObjectLookup.SearchByPath`.

## Individual Comments

### Comment 1
<location> `MCPForUnity/Editor/Helpers/GameObjectLookup.cs:160-161` </location>
<code_context>
+                foreach (var go in allObjects)
+                {
+                    if (go == null) continue;
+                    var goPath = GetGameObjectPath(go);
+                    if (goPath == path || goPath.EndsWith("/" + path))
+                    {
+                        yield return go.GetInstanceID();
</code_context>

<issue_to_address>
**suggestion:** Path-matching logic is duplicated across helpers and tools; consider centralizing.

The `goPath == path || goPath.EndsWith("/" + path)` check is duplicated here and in `ManageGameObjectCommon` (both in the `by_path` branch and the fallback path search). Centralize this into a `SearchByPath`/`MatchesPath` helper on `GameObjectLookup` and call it from both places to keep semantics (case sensitivity, separators, search modes) consistent and easier to change later.
</issue_to_address>

### Comment 2
<location> `MCPForUnity/Editor/Tools/GameObjects/ManageGameObjectCommon.cs:92-100` </location>
<code_context>
-            
-            var found = GameObject.Find(path);
-            if (found != null)
+            else
             {
-                yield return found.GetInstanceID();
</code_context>

<issue_to_address>
**suggestion (performance):** Full-scene scan for by_path searches may be unnecessarily expensive outside Prefab Stage.

When `rootSearchObject == null`, this now always calls `GetAllSceneObjects(searchInactive)` and scans the whole scene, even in normal mode. Previously, non-prefab lookups used `GameObject.Find(searchTerm)` for active objects, which is cheaper. For `searchInactive == false` outside Prefab Stage, consider keeping a fast path (e.g., `GameObject.Find` or `GameObjectLookup.SearchByPath`) and only falling back to a full scan when inactive objects are needed or when in Prefab Stage.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +160 to +161
var goPath = GetGameObjectPath(go);
if (goPath == path || goPath.EndsWith("/" + path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Path-matching logic is duplicated across helpers and tools; consider centralizing.

The goPath == path || goPath.EndsWith("/" + path) check is duplicated here and in ManageGameObjectCommon (both in the by_path branch and the fallback path search). Centralize this into a SearchByPath/MatchesPath helper on GameObjectLookup and call it from both places to keep semantics (case sensitivity, separators, search modes) consistent and easier to change later.

Comment on lines +92 to +100
else
{
// In Prefab Stage, GameObject.Find() doesn't work, need to search manually
var allObjects = GetAllSceneObjects(searchInactive);
foreach (var go in allObjects)
{
if (go == null) continue;
var goPath = GameObjectLookup.GetGameObjectPath(go);
if (goPath == searchTerm || goPath.EndsWith("/" + searchTerm))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Full-scene scan for by_path searches may be unnecessarily expensive outside Prefab Stage.

When rootSearchObject == null, this now always calls GetAllSceneObjects(searchInactive) and scans the whole scene, even in normal mode. Previously, non-prefab lookups used GameObject.Find(searchTerm) for active objects, which is cheaper. For searchInactive == false outside Prefab Stage, consider keeping a fast path (e.g., GameObject.Find or GameObjectLookup.SearchByPath) and only falling back to a full scan when inactive objects are needed or when in Prefab Stage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Tools/ManageScene.cs (1)

612-616: ResolveGameObject still uses GameObject.Find() which doesn't work in Prefab Stage.

While GetSceneHierarchyPaged now correctly uses the Prefab Stage scene, ResolveGameObject still relies on GameObject.Find(s) for path-based lookups (Line 615). This will fail when specifying a parent parameter in Prefab Stage mode.

Consider updating the path-based lookup to use the same pattern as GameObjectLookup.SearchByPath:

Proposed fix
             // Path-based find (e.g., "Root/Child/GrandChild")
             if (s.Contains("/"))
             {
-                try { return GameObject.Find(s); } catch { }
+                try
+                {
+                    var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
+                    if (prefabStage != null && prefabStage.prefabContentsRoot != null)
+                    {
+                        // Search in prefab contents
+                        var root = prefabStage.prefabContentsRoot;
+                        if (GetGameObjectPath(root) == s) return root;
+                        var found = root.transform.Find(s);
+                        if (found != null) return found.gameObject;
+                        // Try relative path from root
+                        foreach (Transform t in root.GetComponentsInChildren<Transform>(true))
+                        {
+                            if (GetGameObjectPath(t.gameObject) == s) return t.gameObject;
+                        }
+                        return null;
+                    }
+                    return GameObject.Find(s);
+                }
+                catch { }
             }

@dsarno
Copy link
Collaborator

dsarno commented Jan 17, 2026

Code Review

Overall, this is a solid PR that addresses issue #547 comprehensively. The changes add Prefab Stage support while also improving inactive object handling. However, there are a few outstanding issues from other reviewers that should be addressed before merging:

✅ Strengths

  1. Comprehensive Prefab Stage Support: All three files correctly check for Prefab Stage first, ensuring consistent behavior across the codebase.

  2. Code Deduplication: Centralizing GetAllSceneObjects() in GameObjectLookup and having ManageGameObjectCommon delegate to it is excellent - reduces duplication and ensures consistent behavior.

  3. Improved Inactive Object Support: The manual enumeration approach in SearchByPath() now properly supports inactive objects, which is a nice bonus improvement.

  4. Consistent Pattern: The Prefab Stage detection pattern (PrefabStageUtility.GetCurrentPrefabStage()) is applied consistently across all three files.

⚠️ Issues to Address

1. Path Matching Logic Duplication (Sourcery AI feedback)

The path matching logic goPath == path || goPath.EndsWith("/" + path) is duplicated in multiple places:

  • GameObjectLookup.SearchByPath() (lines ~20, ~49)
  • ManageGameObjectCommon.FindObjectsInternal() - by_path case (line ~24)
  • ManageGameObjectCommon.FindObjectsInternal() - fallback path search (line ~41)

Request: Please extract a shared helper method in GameObjectLookup to centralize this logic, e.g.:

private static bool MatchesPath(GameObject go, string path)
{
    if (go == null) return false;
    var goPath = GetGameObjectPath(go);
    return goPath == path || goPath.EndsWith("/" + path);
}

Then use this helper in all three locations to keep semantics consistent and easier to maintain.

2. Performance Regression in Normal Scene Mode (Sourcery AI feedback)

In ManageGameObjectCommon.FindObjectsInternal() by_path branch (lines 16-29), when rootSearchObject == null, the code always scans all scene objects, even in normal scene mode when searchInactive=false. This is a performance regression - previously it used GameObject.Find() which is faster for active objects.

Request: Please gate the manual scan on Prefab Stage only, similar to how GameObjectLookup.SearchByPath() does it:

else
{
    var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
    if (prefabStage != null || searchInactive)
    {
        // Manual scan for Prefab Stage or inactive objects
        var allObjects = GetAllSceneObjects(searchInactive);
        foreach (var go in allObjects)
        {
            if (go == null) continue;
            var goPath = GameObjectLookup.GetGameObjectPath(go);
            if (goPath == searchTerm || goPath.EndsWith("/" + searchTerm))
            {
                results.Add(go);
            }
        }
    }
    else
    {
        // Fast path for normal scene mode with active objects only
        var found = GameObject.Find(searchTerm);
        if (found != null) results.Add(found);
    }
}

3. ResolveGameObject Still Uses GameObject.Find() (CodeRabbit AI feedback)

ResolveGameObject in ManageScene.cs (around line 612-616) still uses GameObject.Find(s) for path-based lookups, which doesn't work in Prefab Stage.

Request: Please update ResolveGameObject to use GameObjectLookup.SearchByPath() or implement the same Prefab Stage-aware pattern used elsewhere in the PR.

📝 Additional Suggestions

  1. Path Matching Edge Case (Minor):
    The path matching logic goPath.EndsWith("/" + path) could potentially match unintended paths. For example, path "Child" would match both "Parent/Child" ✅ (intended) but also "Parent/SomeChild" ❌ (unintended). Consider using a more precise match or splitting by "/" and checking the last segment matches exactly.

  2. Testing: Consider testing nested prefabs, prefabs with inactive GameObjects, and edge cases like empty prefabs.

Verdict

Request Changes - Please address the three issues above (especially #2 which introduces a performance regression) before merging. Once these are fixed, this will be an excellent implementation! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

find_gameobjects and GetAllSceneObjects fail to find nodes when editing prefab in Prefab Stage

2 participants