using System;
using System.Collections.Generic;
using System.Reflection;
using Codely.Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Tools
{
///
/// Manages the Game view resolution (get, set, list).
/// Uses reflection to access Unity's internal GameView and GameViewSizes APIs,
/// which are not part of the public editor scripting surface.
///
/// Actions:
/// get_resolution – Return the currently selected Game view resolution.
/// set_resolution – Select (or create) a fixed resolution. Params: width, height (1-8192).
/// list_resolutions – List every resolution available in the current size group.
///
public static class ManageGameView
{
// ---- Cached reflection handles ----
private static bool _reflectionInitialized;
private static string _reflectionError;
private static Type _gameViewType;
private static Type _gameViewSizesType;
private static Type _gameViewSizeType;
private static Type _gameViewSizeGroupType;
private static Type _gameViewSizeTypeEnum;
private static MethodInfo _getMainGameView;
private static PropertyInfo _selectedSizeIndex;
private static PropertyInfo _sizesInstance;
private static PropertyInfo _currentGroupType;
private static PropertyInfo _currentGroupProp; // Unity 6000.x: GameViewSizes.currentGroup
private static MethodInfo _getGroup;
private static MethodInfo _getTotalCount;
private static MethodInfo _getGameViewSize;
private static MethodInfo _addCustomSize;
private static MethodInfo _saveToHardDisk;
private static PropertyInfo _sizeWidth;
private static PropertyInfo _sizeHeight;
private static PropertyInfo _sizeBaseText;
private static PropertyInfo _sizeSizeType;
private static object _fixedResolutionValue;
private static readonly Dictionary> ActionHandlers =
new Dictionary>
{
{ "get_resolution", _ => GetResolution() },
{ "set_resolution", SetResolution },
{ "list_resolutions", _ => ListResolutions() },
};
public static object HandleCommand(JObject @params)
{
if (!TryInitReflection(out var error))
return Response.Error(error);
return ActionRouter.Route(@params, ActionHandlers);
}
// ==================== Reflection setup ====================
private static bool TryInitReflection(out string error)
{
error = null;
if (_reflectionInitialized)
{
error = _reflectionError;
return _reflectionError == null;
}
_reflectionInitialized = true;
try
{
_gameViewType = Type.GetType("UnityEditor.GameView,UnityEditor");
_gameViewSizesType = Type.GetType("UnityEditor.GameViewSizes,UnityEditor");
_gameViewSizeType = Type.GetType("UnityEditor.GameViewSize,UnityEditor");
_gameViewSizeGroupType = Type.GetType("UnityEditor.GameViewSizeGroup,UnityEditor");
_gameViewSizeTypeEnum = Type.GetType("UnityEditor.GameViewSizeType,UnityEditor");
if (_gameViewType == null || _gameViewSizesType == null ||
_gameViewSizeType == null || _gameViewSizeGroupType == null ||
_gameViewSizeTypeEnum == null)
{
_reflectionError = "GameView internals not available in this Unity version.";
error = _reflectionError;
return false;
}
var allFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
// GameView
_getMainGameView = _gameViewType.GetMethod("GetMainGameView", allFlags)
?? _gameViewType.GetMethod("GetMainPlayModeView", allFlags);
_selectedSizeIndex = _gameViewType.GetProperty("selectedSizeIndex", allFlags);
// GameViewSizes - Unity 6000.x uses ScriptableSingleton, check base type too
_sizesInstance = _gameViewSizesType.GetProperty("instance", allFlags);
if (_sizesInstance == null)
{
// ScriptableSingleton.instance is on the base generic type
var baseType = _gameViewSizesType.BaseType;
while (baseType != null && _sizesInstance == null)
{
_sizesInstance = baseType.GetProperty("instance", allFlags);
baseType = baseType.BaseType;
}
}
_currentGroupType = _gameViewSizesType.GetProperty("currentGroupType", allFlags);
_getGroup = _gameViewSizesType.GetMethod("GetGroup", allFlags);
// Unity 6000.x: direct currentGroup property
_currentGroupProp = _gameViewSizesType.GetProperty("currentGroup", allFlags);
_saveToHardDisk = _gameViewSizesType.GetMethod("SaveToHDD",
BindingFlags.Public | BindingFlags.Instance);
// GameViewSizeGroup
_getTotalCount = _gameViewSizeGroupType.GetMethod("GetTotalCount",
BindingFlags.Public | BindingFlags.Instance);
_getGameViewSize = _gameViewSizeGroupType.GetMethod("GetGameViewSize",
BindingFlags.Public | BindingFlags.Instance);
_addCustomSize = _gameViewSizeGroupType.GetMethod("AddCustomSize",
BindingFlags.Public | BindingFlags.Instance);
// GameViewSize properties
_sizeWidth = _gameViewSizeType.GetProperty("width", BindingFlags.Public | BindingFlags.Instance);
_sizeHeight = _gameViewSizeType.GetProperty("height", BindingFlags.Public | BindingFlags.Instance);
_sizeBaseText = _gameViewSizeType.GetProperty("baseText", BindingFlags.Public | BindingFlags.Instance);
_sizeSizeType = _gameViewSizeType.GetProperty("sizeType", BindingFlags.Public | BindingFlags.Instance);
// FixedResolution enum value
_fixedResolutionValue = Enum.Parse(_gameViewSizeTypeEnum, "FixedResolution");
// Validate critical members
var missing = new List();
if (_sizesInstance == null) missing.Add("GameViewSizes.instance");
if (_getGroup == null && _currentGroupProp == null) missing.Add("GameViewSizes.GetGroup/currentGroup");
if (_getTotalCount == null) missing.Add("GameViewSizeGroup.GetTotalCount");
if (_getGameViewSize == null) missing.Add("GameViewSizeGroup.GetGameViewSize");
if (_sizeWidth == null) missing.Add("GameViewSize.width");
if (_sizeHeight == null) missing.Add("GameViewSize.height");
if (_selectedSizeIndex == null) missing.Add("GameView.selectedSizeIndex");
if (missing.Count > 0)
{
_reflectionError = $"Missing GameView internal members: {string.Join(", ", missing)}. This Unity version may not be supported.";
error = _reflectionError;
return false;
}
return true;
}
catch (Exception ex)
{
_reflectionError = $"Failed to initialize GameView reflection: {ex.Message}";
error = _reflectionError;
return false;
}
}
// ==================== Actions ====================
private static object GetResolution()
{
var gameView = GetMainGameView(true);
var group = GetCurrentSizeGroup();
if (group == null)
return Response.Error("Could not access Game view size group.");
int selectedIndex = gameView != null ? GetSelectedSizeIndex(gameView) : -1;
int width = 0;
int height = 0;
string name = "";
string sizeType = "";
if (selectedIndex >= 0 && selectedIndex < GetTotalCount(group))
{
var size = GetGameViewSize(group, selectedIndex);
width = GetSizeWidth(size);
height = GetSizeHeight(size);
name = GetSizeBaseText(size);
sizeType = GetSizeType(size);
}
else if (gameView != null)
{
// Fallback: read targetSize
var targetSizeProp = gameView.GetType().GetProperty("targetSize",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (targetSizeProp != null)
{
var targetSize = (Vector2)targetSizeProp.GetValue(gameView);
width = (int)targetSize.x;
height = (int)targetSize.y;
name = "Unknown";
sizeType = "Unknown";
}
}
// Last-resort fallback: ask the editor for the main Game view render size. This
// works even when the window instance / selected-size index can't be resolved
// (simulator windows, or Tuanjie's separate editor module assemblies).
if (width <= 0 || height <= 0)
{
Vector2 mainSize = Handles.GetMainGameViewSize();
width = (int)mainSize.x;
height = (int)mainSize.y;
if (string.IsNullOrEmpty(name)) name = "Unknown";
if (string.IsNullOrEmpty(sizeType)) sizeType = "Unknown";
}
if (width <= 0 || height <= 0)
return Response.Error("No Game view window found. Open the Game view in the Unity Editor first.");
return Response.Success(
$"Current Game view resolution: {width}x{height}.",
new
{
action = "get_resolution",
width,
height,
name,
selectedIndex,
sizeType
});
}
private static object SetResolution(JObject @params)
{
int width = @params["width"]?.ToObject() ?? 0;
int height = @params["height"]?.ToObject() ?? 0;
if (width <= 0 || height <= 0)
return Response.Error("Parameters 'width' and 'height' are required and must be positive integers.");
if (width > 8192 || height > 8192)
return Response.Error($"Resolution {width}x{height} exceeds maximum allowed (8192x8192).");
var gameView = GetMainGameView(true);
if (gameView == null)
return Response.Error("No Game view window found. Open the Game view in the Unity Editor first.");
var group = GetCurrentSizeGroup();
if (group == null)
return Response.Error("Could not access Game view size group.");
int foundIndex = FindResolution(group, width, height);
bool wasAdded = false;
string label = $"Codely {width}x{height}";
if (foundIndex < 0)
{
// Add custom resolution
foundIndex = AddCustomResolution(group, width, height, label);
wasAdded = true;
}
else
{
var size = GetGameViewSize(group, foundIndex);
label = GetSizeBaseText(size);
if (string.IsNullOrEmpty(label))
label = $"{width}x{height}";
}
SetSelectedSizeIndex(gameView, foundIndex);
// Repaint to apply the change
((EditorWindow)gameView).Repaint();
return Response.Success(
$"Game view resolution set to {width}x{height}.",
new
{
action = "set_resolution",
width,
height,
selectedIndex = foundIndex,
wasAdded,
label
});
}
private static object ListResolutions()
{
var group = GetCurrentSizeGroup();
if (group == null)
return Response.Error("Could not access Game view size group.");
int totalCount = GetTotalCount(group);
var resolutions = new List