This commit is contained in:
Даниил Заикин
2026-06-17 20:44:53 +03:00
parent 9d153773c2
commit b133e7c656
1880 changed files with 244545 additions and 0 deletions
@@ -0,0 +1,375 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class CameraScreenshotTool : EditorWindow
{
private enum CaptureMode
{
CameraOnly,
GameView
}
private Camera[] sceneCameras = Array.Empty<Camera>();
private string[] cameraNames = Array.Empty<string>();
private int selectedCameraIndex = -1;
private Camera targetCamera;
private int width = 1920;
private int height = 1080;
private string fileName = "screenshot";
private string folderName = "Screenshots";
private bool addTimestamp = true;
private bool openFolder = true;
private bool includeUI = false;
private CaptureMode captureMode = CaptureMode.CameraOnly;
private Vector2 scroll;
[MenuItem("Tools/QA/Camera Screenshot Tool")]
public static void Init()
{
var window = GetWindow<CameraScreenshotTool>("Screenshot Tool");
window.minSize = new Vector2(380, 360);
window.RefreshCameraList();
}
private void OnEnable()
{
RefreshCameraList();
EditorApplication.hierarchyChanged += RefreshCameraList;
}
private void OnDisable()
{
EditorApplication.hierarchyChanged -= RefreshCameraList;
}
private void OnFocus()
{
RefreshCameraList();
}
private void OnGUI()
{
scroll = EditorGUILayout.BeginScrollView(scroll);
GUILayout.Space(6);
DrawCameraSection();
GUILayout.Space(10);
DrawCaptureSection();
GUILayout.Space(10);
DrawResolutionSection();
GUILayout.Space(10);
DrawOutputSection();
GUILayout.Space(12);
DrawCaptureButton();
EditorGUILayout.EndScrollView();
}
private void DrawCameraSection()
{
EditorGUILayout.LabelField("Camera", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUI.BeginChangeCheck();
selectedCameraIndex = EditorGUILayout.Popup("Scene Camera", selectedCameraIndex, cameraNames);
if (EditorGUI.EndChangeCheck())
{
targetCamera = GetCameraByIndex(selectedCameraIndex);
}
if (GUILayout.Button("Refresh", GUILayout.Width(70)))
{
RefreshCameraList();
}
}
EditorGUI.BeginChangeCheck();
targetCamera = (Camera)EditorGUILayout.ObjectField("Target Camera", targetCamera, typeof(Camera), true);
if (EditorGUI.EndChangeCheck())
{
SyncDropdownWithTargetCamera();
}
if (targetCamera == null)
{
EditorGUILayout.HelpBox("Выбери камеру со сцены.", MessageType.Info);
}
else
{
EditorGUILayout.HelpBox(
$"Selected: {targetCamera.name}\n" +
$"Enabled: {targetCamera.enabled}\n" +
$"Type: {targetCamera.cameraType}",
MessageType.None
);
}
}
private void DrawCaptureSection()
{
EditorGUILayout.LabelField("Capture", EditorStyles.boldLabel);
captureMode = (CaptureMode)EditorGUILayout.EnumPopup("Mode", captureMode);
includeUI = EditorGUILayout.Toggle("Include UI", includeUI);
if (includeUI)
{
EditorGUILayout.HelpBox(
"Overlay UI обычно не попадает в CameraOnly. Для UI-захвата лучше использовать GameView mode.",
MessageType.Warning
);
}
if (captureMode == CaptureMode.CameraOnly && includeUI)
{
EditorGUILayout.HelpBox(
"В режиме CameraOnly попадут World Space и часть Screen Space - Camera UI. " +
"Screen Space - Overlay UI обычно не попадет.",
MessageType.Info
);
}
if (captureMode == CaptureMode.GameView)
{
EditorGUILayout.HelpBox(
"GameView mode захватывает итоговую картинку, включая overlay UI, " +
"но итоговое разрешение зависит от размера GameView.",
MessageType.Info
);
}
}
private void DrawResolutionSection()
{
EditorGUILayout.LabelField("Resolution", EditorStyles.boldLabel);
width = Mathf.Max(1, EditorGUILayout.IntField("Width", width));
height = Mathf.Max(1, EditorGUILayout.IntField("Height", height));
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("HD"))
{
width = 1280;
height = 720;
}
if (GUILayout.Button("Full HD"))
{
width = 1920;
height = 1080;
}
if (GUILayout.Button("2K"))
{
width = 2560;
height = 1440;
}
if (GUILayout.Button("4K"))
{
width = 3840;
height = 2160;
}
}
}
private void DrawOutputSection()
{
EditorGUILayout.LabelField("Output", EditorStyles.boldLabel);
fileName = EditorGUILayout.TextField("File Name", fileName);
folderName = EditorGUILayout.TextField("Folder", folderName);
addTimestamp = EditorGUILayout.Toggle("Add Timestamp", addTimestamp);
openFolder = EditorGUILayout.Toggle("Open Folder", openFolder);
}
private void DrawCaptureButton()
{
bool canCapture =
captureMode == CaptureMode.GameView ||
targetCamera != null;
GUI.enabled = canCapture;
if (GUILayout.Button("Capture Screenshot", GUILayout.Height(36)))
{
Capture();
}
GUI.enabled = true;
}
private void Capture()
{
string path = BuildOutputPath();
try
{
if (captureMode == CaptureMode.GameView)
{
CaptureGameView(path);
}
else
{
CaptureFromCamera(path, targetCamera, includeUI);
}
Debug.Log($"Screenshot saved: {path}");
AssetDatabase.Refresh();
if (openFolder)
{
EditorUtility.RevealInFinder(path);
}
}
catch (Exception e)
{
Debug.LogError($"Screenshot capture failed: {e}");
}
}
private void CaptureFromCamera(string path, Camera cam, bool withUI)
{
if (cam == null)
throw new InvalidOperationException("Target camera is null.");
int w = Mathf.Max(1, width);
int h = Mathf.Max(1, height);
RenderTexture rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32);
Texture2D tex = new Texture2D(w, h, TextureFormat.RGBA32, false);
RenderTexture previousActive = RenderTexture.active;
RenderTexture previousTarget = cam.targetTexture;
try
{
// Для CameraOnly UI overlay не поддерживается полноценно.
// Но world-space и screen-space camera UI могут попасть,
// если canvas привязан к этой камере.
cam.targetTexture = rt;
RenderTexture.active = rt;
cam.Render();
tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
tex.Apply();
byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes(path, bytes);
}
finally
{
cam.targetTexture = previousTarget;
RenderTexture.active = previousActive;
DestroyImmediate(rt);
DestroyImmediate(tex);
}
}
private void CaptureGameView(string path)
{
// В этом режиме includeUI фактически всегда true по смыслу,
// потому что захватывается итоговый GameView.
// Разрешение тут зависит от фактического размера GameView.
ScreenCapture.CaptureScreenshot(path);
}
private string BuildOutputPath()
{
string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
string targetFolder = Path.Combine(projectPath, folderName);
if (!Directory.Exists(targetFolder))
Directory.CreateDirectory(targetFolder);
string safeName = string.IsNullOrWhiteSpace(fileName) ? "screenshot" : fileName;
foreach (char c in Path.GetInvalidFileNameChars())
safeName = safeName.Replace(c, '_');
if (addTimestamp)
{
safeName += "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
}
return Path.Combine(targetFolder, safeName + ".png");
}
private void RefreshCameraList()
{
sceneCameras = FindObjectsByType<Camera>(FindObjectsSortMode.None)
.Where(c => c.gameObject.scene.IsValid())
.OrderBy(c => c.name)
.ToArray();
if (sceneCameras.Length == 0)
{
cameraNames = new[] { "No cameras found" };
selectedCameraIndex = -1;
targetCamera = null;
Repaint();
return;
}
cameraNames = sceneCameras
.Select(c =>
{
string tag = c.CompareTag("MainCamera") ? " [Main]" : "";
string state = c.enabled ? "" : " [Disabled]";
return $"{c.name}{tag}{state}";
})
.ToArray();
if (targetCamera == null)
{
targetCamera = sceneCameras.FirstOrDefault(c => c.CompareTag("MainCamera")) ?? sceneCameras[0];
}
SyncDropdownWithTargetCamera();
Repaint();
}
private void SyncDropdownWithTargetCamera()
{
if (targetCamera == null || sceneCameras == null || sceneCameras.Length == 0)
{
selectedCameraIndex = -1;
return;
}
selectedCameraIndex = Array.IndexOf(sceneCameras, targetCamera);
if (selectedCameraIndex < 0 && sceneCameras.Length > 0)
{
selectedCameraIndex = 0;
targetCamera = sceneCameras[0];
}
}
private Camera GetCameraByIndex(int index)
{
if (sceneCameras == null || index < 0 || index >= sceneCameras.Length)
return null;
return sceneCameras[index];
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1477c0a9d0abda84690e1cecd71c4caf