using UnityEngine;
using System;
using System.Collections;
using System.IO;
///
/// 스크린샷 캡처 관리.
/// RenderStreamOutput 이 매 프레임 갱신하는 CaptureTexture/AlphaOutputTexture 를 PNG 로 저장.
/// captureWidth/Height 가 지정되면 캡처 직전에 카메라 targetTexture 를 임시로 그 해상도로
/// 변경해 한 프레임 고해상도 렌더 후 원복한다.
/// 트레이드오프: 그 한 프레임 동안 Spout/NDI 송신도 같은 해상도가 되어 수신측이 잠깐 깜빡임.
///
[Serializable]
public class ScreenshotManager
{
[Tooltip("RenderStreamOutput 컴포넌트 (비어있으면 씬에서 자동 검색)")]
public RenderStreamOutput renderStream;
[Tooltip("스크린샷 저장 경로 (비어있으면 프로젝트 루트의 Screenshots/)")]
public string screenshotSavePath = "";
[Tooltip("파일명 앞에 붙을 접두사")]
public string screenshotFilePrefix = "Screenshot";
[Header("고해상도 캡처 (0 이면 카메라 해상도 사용)")]
[Tooltip("캡처 너비. 0 이면 카메라 해상도 그대로")]
public int captureWidth = 3840;
[Tooltip("캡처 높이. 0 이면 카메라 해상도 그대로")]
public int captureHeight = 2160;
[Tooltip("(하위 호환) 외부 코드가 .screenshotCamera 로 접근하면 RenderStreamOutput.MainCam 반환")]
public Camera screenshotCamera => renderStream != null ? renderStream.MainCam : null;
private MonoBehaviour host;
private Action log;
private Action logError;
public void Initialize(MonoBehaviour host, Action log, Action logError)
{
this.host = host;
this.log = log;
this.logError = logError;
if (renderStream == null)
renderStream = UnityEngine.Object.FindFirstObjectByType();
if (renderStream == null)
logError?.Invoke("RenderStreamOutput 컴포넌트를 찾을 수 없습니다 — 알파 합성 캡처 불가");
if (string.IsNullOrEmpty(screenshotSavePath))
screenshotSavePath = Path.Combine(Application.dataPath, "..", "Screenshots");
if (!Directory.Exists(screenshotSavePath))
{
Directory.CreateDirectory(screenshotSavePath);
log?.Invoke($"Screenshots 폴더 생성됨: {screenshotSavePath}");
}
}
public void CaptureScreenshot()
{
if (!CanCapture()) return;
host.StartCoroutine(CaptureCoroutine(alphaOnly: false));
}
public void CaptureAlphaScreenshot()
{
if (!CanCapture()) return;
host.StartCoroutine(CaptureCoroutine(alphaOnly: true));
}
private bool CanCapture()
{
if (host == null) { logError?.Invoke("ScreenshotManager host(MonoBehaviour) 가 없습니다"); return false; }
if (renderStream == null || renderStream.MainCam == null)
{
logError?.Invoke("RenderStreamOutput / MainCam 이 준비되지 않았습니다");
return false;
}
return true;
}
private IEnumerator CaptureCoroutine(bool alphaOnly)
{
var cam = renderStream.MainCam;
bool useTempResolution = captureWidth > 0 && captureHeight > 0;
RenderTexture tempRT = null;
RenderTexture prevTarget = null;
if (useTempResolution)
{
prevTarget = cam.targetTexture;
var desc = new RenderTextureDescriptor(captureWidth, captureHeight,
renderStream.TextureFormat, 24)
{
msaaSamples = Mathf.Max(1, renderStream.AntiAliasing)
};
tempRT = new RenderTexture(desc);
tempRT.Create();
cam.targetTexture = tempRT;
// 1프레임 — RenderStreamOutput.Update 가 새 pixelWidth 감지 → InitializeTextures(고해상도)
yield return null;
// 1프레임 — 고해상도 RT 에 우리 패스 결과 채워짐
yield return new WaitForEndOfFrame();
}
var srcRT = alphaOnly ? renderStream.AlphaOutputTexture : renderStream.CaptureTexture;
if (srcRT != null)
{
string fileName = GenerateFileName("png", alphaOnly ? "_Alpha" : "");
SaveRtAsPng(srcRT, fileName, alphaOnly ? TextureFormat.RGB24 : TextureFormat.RGBA32);
}
else
{
logError?.Invoke("캡처 RT 가 준비되지 않았습니다");
}
if (useTempResolution)
{
cam.targetTexture = prevTarget;
if (tempRT != null)
{
tempRT.Release();
UnityEngine.Object.Destroy(tempRT);
}
// 한 프레임 더 대기 — RenderStreamOutput.Update 가 원래 해상도로 복귀
yield return null;
}
}
public void OpenScreenshotFolder()
{
if (Directory.Exists(screenshotSavePath))
{
System.Diagnostics.Process.Start(screenshotSavePath);
log?.Invoke($"저장 폴더 열기: {screenshotSavePath}");
}
else
{
logError?.Invoke($"저장 폴더가 존재하지 않습니다: {screenshotSavePath}");
}
}
private void SaveRtAsPng(RenderTexture rt, string fileName, TextureFormat format)
{
string fullPath = Path.Combine(screenshotSavePath, fileName);
RenderTexture prevActive = RenderTexture.active;
Texture2D tex = null;
try
{
RenderTexture.active = rt;
tex = new Texture2D(rt.width, rt.height, format, false);
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
File.WriteAllBytes(fullPath, tex.EncodeToPNG());
log?.Invoke($"스크린샷 저장 완료: {fullPath} ({rt.width}x{rt.height})");
}
catch (Exception e)
{
logError?.Invoke($"스크린샷 저장 실패 ({fileName}): {e.Message}");
}
finally
{
RenderTexture.active = prevActive;
if (tex != null) UnityEngine.Object.Destroy(tex);
}
}
private string GenerateFileName(string extension, string suffix = "")
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
return $"{screenshotFilePrefix}{suffix}_{timestamp}.{extension}";
}
public void Cleanup()
{
// RenderStreamOutput 이 모든 RT/Material lifecycle 을 관리하므로 별도 정리 없음
}
}