diff --git a/Assets/Tests/Editor/GameViewBridgeTests.cs b/Assets/Tests/Editor/GameViewBridgeTests.cs
new file mode 100644
index 0000000000..ae83df5a0b
--- /dev/null
+++ b/Assets/Tests/Editor/GameViewBridgeTests.cs
@@ -0,0 +1,60 @@
+using System.Reflection;
+using NUnit.Framework;
+using UnityEngine;
+
+using io.github.hatayama.UnityCliLoop.InternalAPIBridge;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Verifies PlayModeView RenderTexture field resolution used by GameViewBridge.
+ ///
+ public class GameViewBridgeTests
+ {
+ private class FakePlayModeView
+ {
+#pragma warning disable CS0414 // Field is assigned for reflection-based tests only.
+ private object m_TargetTexture = "texture";
+#pragma warning restore CS0414
+ }
+
+ private class FakeSimulatorWindow : FakePlayModeView
+ {
+ }
+
+ [Test]
+ public void ResolveTargetTextureField_WhenCalledOnDeclaringType_FindsPrivateBaseField()
+ {
+ // Verifies GetField on the PlayModeView declaring type finds m_TargetTexture.
+ FieldInfo field = GameViewBridge.ResolveTargetTextureField(typeof(FakePlayModeView));
+
+ Assert.That(field, Is.Not.Null);
+ Assert.That(field.Name, Is.EqualTo("m_TargetTexture"));
+ Assert.That(field.DeclaringType, Is.EqualTo(typeof(FakePlayModeView)));
+ }
+
+ [Test]
+ public void ResolveTargetTextureField_WhenDerivedTypeGetFieldMissesPrivateBaseField_DeclaringTypeStillResolves()
+ {
+ // Verifies the reflection pitfall: derived-type GetField cannot see private base fields.
+ FieldInfo fromDerived = typeof(FakeSimulatorWindow).GetField(
+ "m_TargetTexture",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ FieldInfo fromDeclaring = GameViewBridge.ResolveTargetTextureField(typeof(FakePlayModeView));
+
+ Assert.That(fromDerived, Is.Null);
+ Assert.That(fromDeclaring, Is.Not.Null);
+
+ FakeSimulatorWindow instance = new();
+ object value = fromDeclaring.GetValue(instance);
+ Assert.That(value, Is.EqualTo("texture"));
+ }
+
+ [Test]
+ public void GetRenderTexture_WhenMembersResolved_DoesNotThrow()
+ {
+ // Verifies PlayModeView type/method/field resolution completes without throwing.
+ Assert.DoesNotThrow(() => GameViewBridge.GetRenderTexture());
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/GameViewBridgeTests.cs.meta b/Assets/Tests/Editor/GameViewBridgeTests.cs.meta
new file mode 100644
index 0000000000..3cee14f1dc
--- /dev/null
+++ b/Assets/Tests/Editor/GameViewBridgeTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 72e1206d01daa484da9b23ccb96473ad
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Tests/Editor/UnityCLILoop.Tests.Editor.asmdef b/Assets/Tests/Editor/UnityCLILoop.Tests.Editor.asmdef
index 22786365d0..c95c782558 100644
--- a/Assets/Tests/Editor/UnityCLILoop.Tests.Editor.asmdef
+++ b/Assets/Tests/Editor/UnityCLILoop.Tests.Editor.asmdef
@@ -3,6 +3,7 @@
"rootNamespace": "io.github.hatayama.UnityCliLoop.Tests.Editor",
"references": [
"GUID:214998e563c124e8a88199b2dd1f522d",
+ "GUID:5079a8d3a72924a81aa1cbc25f65ed1b",
"GUID:5c4588558a3624eacbce0f50007cf1eb",
"GUID:18e6dd30a99d14f32a045f79a2956f46",
"GUID:2fca906c8834d45aea12a5cc143c032d",
diff --git a/Packages/src/Editor/FirstPartyTools/Screenshot/EditorWindowCaptureUtility.cs b/Packages/src/Editor/FirstPartyTools/Screenshot/EditorWindowCaptureUtility.cs
index 09e10a2d8b..e62cf0bebc 100644
--- a/Packages/src/Editor/FirstPartyTools/Screenshot/EditorWindowCaptureUtility.cs
+++ b/Packages/src/Editor/FirstPartyTools/Screenshot/EditorWindowCaptureUtility.cs
@@ -162,7 +162,8 @@ public static string[] GetOpenWindowNames()
return names.ToArray();
}
- // Captures game rendering by reading GameView's composited RenderTexture (PlayMode only).
+ // Captures game rendering by reading the Play Mode view RenderTexture (PlayMode only).
+ // Works for both GameView and Device Simulator via PlayModeView.m_TargetTexture.
// Contains all cameras + Screen Space Overlay Canvas, without tab bar or borders.
internal static async Task<(Texture2D? texture, GameRenderingImageInfo renderingImageInfo, bool timedOut)> CaptureGameRenderingAsync(
float resolutionScale,
@@ -189,13 +190,13 @@ public static string[] GetOpenWindowNames()
RenderTexture rt = GameViewBridge.GetRenderTexture();
if (rt == null)
{
- Debug.LogWarning("[EditorWindowCaptureUtility] GameView RenderTexture is not available");
+ Debug.LogWarning("[EditorWindowCaptureUtility] Play Mode view RenderTexture is not available");
GameRenderingImageInfo unavailableInfo = renderingImageInfo ??
CreateUnavailableGameRenderingImageInfo(Handles.GetMainGameViewSize());
return (null, unavailableInfo, false);
}
- // GameView RenderTexture can be shorter than the full input area, so raw image Y needs this offset.
+ // Play Mode view RenderTexture can be shorter than the full input area, so raw image Y needs this offset.
GameRenderingImageInfo captureInfo = renderingImageInfo ??
CreateGameRenderingImageInfo(Handles.GetMainGameViewSize(), rt.width, rt.height);
diff --git a/Packages/src/Editor/FirstPartyTools/Screenshot/ScreenshotUseCase.cs b/Packages/src/Editor/FirstPartyTools/Screenshot/ScreenshotUseCase.cs
index 3317a49e65..42b5a2ba5c 100644
--- a/Packages/src/Editor/FirstPartyTools/Screenshot/ScreenshotUseCase.cs
+++ b/Packages/src/Editor/FirstPartyTools/Screenshot/ScreenshotUseCase.cs
@@ -168,7 +168,7 @@ private async Task CaptureRenderingAsync(
if (captureTimedOut)
{
return CreateTimedOutResult(
- "GameView rendering capture",
+ "Play Mode view rendering capture",
correlationId,
new List());
}
@@ -190,7 +190,7 @@ private async Task CaptureRenderingAsync(
{
VibeLogger.LogError(
"screenshot_rendering_unavailable",
- "GameView RenderTexture is not available. Open the Game view and wait for a frame before retrying.",
+ "Play Mode view RenderTexture is not available. Open the Game view or Device Simulator and wait for a frame before retrying.",
correlationId: correlationId
);
return new ScreenshotResponse();
diff --git a/Packages/src/Editor/FirstPartyTools/Screenshot/UIElementAnnotator.cs b/Packages/src/Editor/FirstPartyTools/Screenshot/UIElementAnnotator.cs
index a78de55a98..ed182dfbfc 100644
--- a/Packages/src/Editor/FirstPartyTools/Screenshot/UIElementAnnotator.cs
+++ b/Packages/src/Editor/FirstPartyTools/Screenshot/UIElementAnnotator.cs
@@ -9,7 +9,7 @@
namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
{
// Creates a temporary Screen Space Overlay Canvas that draws bounding boxes and labels
- // over interactive UI elements. The overlay is captured by GameView's m_RenderTexture
+ // over interactive UI elements. The overlay is captured by PlayModeView.m_TargetTexture
// (OnGUI-based overlays are NOT included in the RT).
///
/// Provides UI Element Annotator behavior for Unity CLI Loop.
diff --git a/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs b/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs
new file mode 100644
index 0000000000..dfa2c7f965
--- /dev/null
+++ b/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("UnityCLILoop.Tests.Editor")]
diff --git a/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs.meta b/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs.meta
new file mode 100644
index 0000000000..4ccb36acb7
--- /dev/null
+++ b/Packages/src/Editor/InternalAPIBridge/AssemblyInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: df8bc76e5b3594fb98fc546217349bb1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/InternalAPIBridge/GameViewBridge.cs b/Packages/src/Editor/InternalAPIBridge/GameViewBridge.cs
index 518fb86b4a..71bc674e2f 100644
--- a/Packages/src/Editor/InternalAPIBridge/GameViewBridge.cs
+++ b/Packages/src/Editor/InternalAPIBridge/GameViewBridge.cs
@@ -6,56 +6,59 @@
namespace io.github.hatayama.UnityCliLoop.InternalAPIBridge
{
///
- /// Bridge class for accessing Unity GameView internal APIs via reflection.
- /// GameView is an internal class; this bridge discovers members dynamically.
+ /// Bridge for the active Play Mode view RenderTexture via reflection.
+ /// Uses PlayModeView so both GameView and Device Simulator windows work.
///
public static class GameViewBridge
{
- private static Type _gameViewType;
- private static FieldInfo _renderTextureField;
+ private const string PlayModeViewTypeName = "UnityEditor.PlayModeView";
+ private const string GetMainPlayModeViewMethodName = "GetMainPlayModeView";
+ private const string TargetTextureFieldName = "m_TargetTexture";
+
+ private static Type _playModeViewType;
+ private static MethodInfo _getMainPlayModeViewMethod;
+ private static FieldInfo _targetTextureField;
private static bool _memberSearchDone;
///
- /// Get the GameView's composited RenderTexture containing all cameras + Screen Space Overlay Canvas.
+ /// Get the active Play Mode view's composited RenderTexture
+ /// (cameras + Screen Space Overlay Canvas).
///
- /// The RenderTexture, or null if GameView not found or field not accessible
+ /// The RenderTexture, or null if the view or field is unavailable
public static RenderTexture GetRenderTexture()
{
EnsureMembersResolved();
- EditorWindow gameView = FindMainGameView();
- if (gameView == null || _renderTextureField == null)
+ EditorWindow playModeView = FindMainPlayModeView();
+ if (playModeView == null || _targetTextureField == null)
{
return null;
}
- return _renderTextureField.GetValue(gameView) as RenderTexture;
+ return _targetTextureField.GetValue(playModeView) as RenderTexture;
}
- private static EditorWindow FindMainGameView()
+ ///
+ /// Resolve m_TargetTexture on the PlayModeView declaring type.
+ /// Must not use a derived Type: GetField does not return private fields declared on base types.
+ ///
+ internal static FieldInfo ResolveTargetTextureField(Type playModeViewType)
{
- if (_gameViewType == null)
- {
- return null;
- }
+ Debug.Assert(playModeViewType != null, "playModeViewType must not be null");
- UnityEngine.Object[] gameViews = Resources.FindObjectsOfTypeAll(_gameViewType);
- if (gameViews.Length == 0)
- {
- return null;
- }
+ return playModeViewType.GetField(
+ TargetTextureFieldName,
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ }
- // Prefer focused window to match editor's active view context
- foreach (UnityEngine.Object gv in gameViews)
+ private static EditorWindow FindMainPlayModeView()
+ {
+ if (_getMainPlayModeViewMethod == null)
{
- EditorWindow window = gv as EditorWindow;
- if (window != null && window.hasFocus)
- {
- return window;
- }
+ return null;
}
- return gameViews[0] as EditorWindow;
+ return _getMainPlayModeViewMethod.Invoke(null, null) as EditorWindow;
}
private static void EnsureMembersResolved()
@@ -66,18 +69,27 @@ private static void EnsureMembersResolved()
}
_memberSearchDone = true;
- _gameViewType = typeof(Editor).Assembly.GetType("UnityEditor.GameView");
- if (_gameViewType == null)
+ _playModeViewType = typeof(Editor).Assembly.GetType(PlayModeViewTypeName);
+ if (_playModeViewType == null)
{
- Debug.LogWarning("[GameViewBridge] GameView type not found");
+ Debug.LogWarning("[GameViewBridge] PlayModeView type not found");
return;
}
- _renderTextureField = _gameViewType.GetField("m_RenderTexture",
- BindingFlags.Instance | BindingFlags.NonPublic);
- if (_renderTextureField == null)
+ _getMainPlayModeViewMethod = _playModeViewType.GetMethod(
+ GetMainPlayModeViewMethodName,
+ BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
+ if (_getMainPlayModeViewMethod == null)
+ {
+ Debug.LogWarning("[GameViewBridge] GetMainPlayModeView method not found");
+ return;
+ }
+
+ // why: private base fields are invisible to GetField on derived types (GameView / SimulatorWindow)
+ _targetTextureField = ResolveTargetTextureField(_playModeViewType);
+ if (_targetTextureField == null)
{
- Debug.LogWarning("[GameViewBridge] m_RenderTexture field not found");
+ Debug.LogWarning("[GameViewBridge] m_TargetTexture field not found on PlayModeView");
}
}
}