Files
2026-06-17 20:44:53 +03:00

375 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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