#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(); private string[] cameraNames = Array.Empty(); 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("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(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