2025-08-21 13:10:17 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System;
|
|
|
|
|
using Zenith;
|
|
|
|
|
|
|
|
|
|
public class TransparentCaptureSystem<T> : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
[Header("相机设置")] public Camera targetCamera;
|
|
|
|
|
public KeyCode captureKey = KeyCode.C;
|
|
|
|
|
|
|
|
|
|
[Header("原始截图设置")] public int captureWidth = 1080;
|
|
|
|
|
public int captureHeight = 1920;
|
|
|
|
|
|
|
|
|
|
[Header("裁剪设置")] public int cropWidth = 1080;
|
|
|
|
|
public int cropHeight = 1920;
|
|
|
|
|
public Vector2 cropOffset = Vector2.zero; // 裁剪偏移量 (0-1)
|
|
|
|
|
|
|
|
|
|
[Header("透明通道")] public bool preserveAlpha = true;
|
|
|
|
|
public Color backgroundColor = new Color(0, 0, 0, 0); // 透明背景
|
|
|
|
|
|
|
|
|
|
[Header("输出设置")] public string saveFolder = "Screenshots";
|
|
|
|
|
public string fileNamePrefix = "Screenshot";
|
|
|
|
|
public bool saveAsPNG = true;
|
|
|
|
|
public string onlyFileName = "";
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 改为protected,允许子类访问关键资源
|
|
|
|
|
protected RenderTexture _renderTexture;
|
|
|
|
|
protected Texture2D _originalTexture;
|
|
|
|
|
protected Texture2D _croppedTexture;
|
|
|
|
|
protected byte[] bytes; // 图片字节数组,子类可直接使用
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
|
|
|
|
public Transform uiRoot;
|
|
|
|
|
public static T Instance;
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 读取保存路径逻辑保留
|
2025-08-21 13:10:17 +08:00
|
|
|
|
var allText = File.ReadAllText(Application.streamingAssetsPath + "/picSavePath.txt");
|
|
|
|
|
if (string.IsNullOrEmpty(allText))
|
|
|
|
|
{
|
|
|
|
|
#if !UNITY_EDITOR
|
|
|
|
|
saveFolder = Path.Combine(Application.dataPath, "Screenshots");
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
saveFolder = allText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
|
|
|
|
if (targetCamera == null) targetCamera = Camera.main;
|
2025-08-22 00:33:25 +08:00
|
|
|
|
OnStart(); // 启动钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 初始化方法,允许子类重写
|
2025-08-21 13:10:17 +08:00
|
|
|
|
protected virtual void Init()
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
if (Instance == null)
|
|
|
|
|
Instance = (T)(object)this;
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 主流程:截图并裁剪(保留原有逻辑)
|
2025-08-21 13:10:17 +08:00
|
|
|
|
public IEnumerator CaptureAndCrop(Action<byte[]> callback = null)
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
OnBeforeCapture(); // 截图前钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
ValidateSizes();
|
|
|
|
|
|
|
|
|
|
// 创建原始截图
|
|
|
|
|
yield return StartCoroutine(CaptureOriginalScreenshot());
|
2025-08-22 00:33:25 +08:00
|
|
|
|
OnAfterCaptureOriginal(); // 原始截图后钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 裁剪图片
|
2025-08-21 13:10:17 +08:00
|
|
|
|
CropWithTransparency();
|
2025-08-22 00:33:25 +08:00
|
|
|
|
OnAfterCrop(); // 裁剪后钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
|
|
|
|
// 保存结果
|
|
|
|
|
SaveCroppedImage();
|
2025-08-22 00:33:25 +08:00
|
|
|
|
OnAfterSave(); // 保存后钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
|
|
|
|
// 清理资源
|
|
|
|
|
CleanupResources();
|
|
|
|
|
callback?.Invoke(bytes);
|
2025-08-22 00:33:25 +08:00
|
|
|
|
|
|
|
|
|
OnCaptureComplete(); // 完整流程结束钩子
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
2025-08-22 00:33:25 +08:00
|
|
|
|
|
|
|
|
|
// 【核心】原始截图方法(标记为virtual,允许子类重写)
|
|
|
|
|
protected virtual IEnumerator CaptureOriginalScreenshot()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 保存相机原始状态
|
2025-08-21 13:10:17 +08:00
|
|
|
|
RenderTexture originalRT = targetCamera.targetTexture;
|
|
|
|
|
CameraClearFlags originalClearFlags = targetCamera.clearFlags;
|
|
|
|
|
Color originalBackgroundColor = targetCamera.backgroundColor;
|
2025-08-22 00:33:25 +08:00
|
|
|
|
bool originalEnabled = targetCamera.enabled;
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 创建临时RenderTexture
|
2025-08-21 13:10:17 +08:00
|
|
|
|
_renderTexture = RenderTexture.GetTemporary(
|
2025-08-22 00:33:25 +08:00
|
|
|
|
captureWidth,
|
|
|
|
|
captureHeight,
|
|
|
|
|
32,
|
2025-08-21 13:10:17 +08:00
|
|
|
|
RenderTextureFormat.ARGB32,
|
|
|
|
|
RenderTextureReadWrite.Linear
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 配置相机仅渲染到RT
|
|
|
|
|
targetCamera.enabled = false;
|
2025-08-21 13:10:17 +08:00
|
|
|
|
targetCamera.targetTexture = _renderTexture;
|
|
|
|
|
targetCamera.clearFlags = CameraClearFlags.SolidColor;
|
|
|
|
|
targetCamera.backgroundColor = preserveAlpha ? backgroundColor : Color.black;
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 手动渲染
|
2025-08-21 13:10:17 +08:00
|
|
|
|
targetCamera.Render();
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 读取像素到Texture2D
|
2025-08-21 13:10:17 +08:00
|
|
|
|
RenderTexture.active = _renderTexture;
|
|
|
|
|
_originalTexture = new Texture2D(captureWidth, captureHeight,
|
|
|
|
|
preserveAlpha ? TextureFormat.ARGB32 : TextureFormat.RGB24, false);
|
|
|
|
|
_originalTexture.ReadPixels(new Rect(0, 0, captureWidth, captureHeight), 0, 0);
|
|
|
|
|
_originalTexture.Apply();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 恢复相机设置
|
2025-08-21 13:10:17 +08:00
|
|
|
|
targetCamera.targetTexture = originalRT;
|
|
|
|
|
targetCamera.clearFlags = originalClearFlags;
|
|
|
|
|
targetCamera.backgroundColor = originalBackgroundColor;
|
|
|
|
|
targetCamera.enabled = originalEnabled;
|
2025-08-22 00:33:25 +08:00
|
|
|
|
RenderTexture.active = null;
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 裁剪逻辑(标记为virtual)
|
|
|
|
|
protected virtual void CropWithTransparency()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
if (_originalTexture == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("原始截图未创建!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算裁剪起始位置
|
|
|
|
|
int startX = Mathf.FloorToInt((_originalTexture.width - cropWidth) * cropOffset.x);
|
|
|
|
|
int startY = Mathf.FloorToInt((_originalTexture.height - cropHeight) * cropOffset.y);
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 边界检查
|
2025-08-21 13:10:17 +08:00
|
|
|
|
startX = Mathf.Clamp(startX, 0, _originalTexture.width - cropWidth);
|
|
|
|
|
startY = Mathf.Clamp(startY, 0, _originalTexture.height - cropHeight);
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 创建裁剪纹理
|
2025-08-21 13:10:17 +08:00
|
|
|
|
_croppedTexture = new Texture2D(cropWidth, cropHeight,
|
2025-08-22 00:33:25 +08:00
|
|
|
|
preserveAlpha ? TextureFormat.ARGB32 : TextureFormat.RGB24, false);
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 设置像素
|
2025-08-21 13:10:17 +08:00
|
|
|
|
Color[] pixels = _originalTexture.GetPixels(startX, startY, cropWidth, cropHeight);
|
|
|
|
|
_croppedTexture.SetPixels(pixels);
|
|
|
|
|
_croppedTexture.Apply();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 保存逻辑(标记为virtual)
|
|
|
|
|
protected virtual void SaveCroppedImage()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
if (_croppedTexture == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("裁剪后的纹理未创建!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建保存目录
|
|
|
|
|
string folderPath = saveFolder;
|
|
|
|
|
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
|
|
|
|
|
|
|
|
|
|
// 生成文件名
|
|
|
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
|
|
|
string extension = saveAsPNG ? ".png" : ".jpg";
|
2025-08-22 00:33:25 +08:00
|
|
|
|
string fileName = string.IsNullOrEmpty(onlyFileName)
|
|
|
|
|
? $"{fileNamePrefix}_{cropWidth}x{cropHeight}_{timestamp}{extension}"
|
|
|
|
|
: $"{onlyFileName}{extension}";
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
|
|
|
|
fullPath = Path.Combine(folderPath, fileName);
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 编码图片
|
|
|
|
|
byte[] imageBytes = saveAsPNG
|
|
|
|
|
? _croppedTexture.EncodeToPNG()
|
|
|
|
|
: EncodeToJPG(_croppedTexture);
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 保存并读取字节数组
|
2025-08-21 13:10:17 +08:00
|
|
|
|
ImageDpiProcessor.SaveTextureWithDpi(_croppedTexture, fullPath, 300);
|
2025-08-22 00:33:25 +08:00
|
|
|
|
fullPath = new FileInfo(fullPath).FullName;
|
2025-08-21 13:10:17 +08:00
|
|
|
|
Debug.Log($"截图已保存至: {fullPath}");
|
|
|
|
|
bytes = File.ReadAllBytes(fullPath);
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
UnityEditor.EditorUtility.RevealInFinder(fullPath);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// JPG编码辅助方法(protected,允许子类复用)
|
|
|
|
|
protected byte[] EncodeToJPG(Texture2D texture)
|
|
|
|
|
{
|
|
|
|
|
Texture2D jpgTexture = new Texture2D(texture.width, texture.height, TextureFormat.RGB24, false);
|
|
|
|
|
jpgTexture.SetPixels(texture.GetPixels());
|
|
|
|
|
jpgTexture.Apply();
|
|
|
|
|
byte[] bytes = jpgTexture.EncodeToJPG(90);
|
|
|
|
|
Destroy(jpgTexture);
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 尺寸验证(标记为virtual)
|
|
|
|
|
protected virtual void ValidateSizes()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
captureWidth = Mathf.Clamp(captureWidth, 32, SystemInfo.maxTextureSize);
|
|
|
|
|
captureHeight = Mathf.Clamp(captureHeight, 32, SystemInfo.maxTextureSize);
|
|
|
|
|
cropWidth = Mathf.Clamp(cropWidth, 32, captureWidth);
|
|
|
|
|
cropHeight = Mathf.Clamp(cropHeight, 32, captureHeight);
|
|
|
|
|
cropOffset.x = Mathf.Clamp01(cropOffset.x);
|
|
|
|
|
cropOffset.y = Mathf.Clamp01(cropOffset.y);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 资源清理(标记为virtual)
|
|
|
|
|
protected virtual void CleanupResources()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
if (_renderTexture != null)
|
|
|
|
|
{
|
2025-08-22 00:33:25 +08:00
|
|
|
|
RenderTexture.ReleaseTemporary(_renderTexture);
|
2025-08-21 13:10:17 +08:00
|
|
|
|
_renderTexture = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_originalTexture != null)
|
|
|
|
|
{
|
|
|
|
|
Destroy(_originalTexture);
|
|
|
|
|
_originalTexture = null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
if (_croppedTexture != null)
|
|
|
|
|
{
|
|
|
|
|
Destroy(_croppedTexture);
|
|
|
|
|
_croppedTexture = null;
|
|
|
|
|
}
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 立即截图方法(保留)
|
2025-08-21 13:10:17 +08:00
|
|
|
|
[ContextMenu("立即截图并裁剪")]
|
|
|
|
|
public virtual void CaptureAndCropNow(Action<byte[]> callback = null)
|
|
|
|
|
{
|
|
|
|
|
StartCoroutine(CaptureAndCrop(callback));
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// GUI预览(标记为virtual)
|
|
|
|
|
protected virtual void OnGUI()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
// 显示原始截图预览
|
|
|
|
|
if (_originalTexture != null)
|
|
|
|
|
{
|
|
|
|
|
GUI.DrawTexture(new Rect(10, 10, 200, 200 * (float)captureHeight / captureWidth),
|
|
|
|
|
_originalTexture, ScaleMode.ScaleToFit);
|
|
|
|
|
GUI.Label(new Rect(10, 220, 200, 30), $"原始尺寸: {captureWidth}x{captureHeight}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示裁剪后截图预览
|
|
|
|
|
if (_croppedTexture != null)
|
|
|
|
|
{
|
|
|
|
|
GUI.DrawTexture(new Rect(Screen.width - 210, 10, 200, 200 * (float)cropHeight / cropWidth),
|
|
|
|
|
_croppedTexture, ScaleMode.ScaleToFit);
|
|
|
|
|
GUI.Label(new Rect(Screen.width - 210, 220, 200, 30), $"裁剪尺寸: {cropWidth}x{cropHeight}");
|
|
|
|
|
|
|
|
|
|
if (preserveAlpha)
|
|
|
|
|
{
|
|
|
|
|
GUI.Label(new Rect(Screen.width - 210, 250, 200, 30), "包含透明通道");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// 销毁时清理(保留)
|
|
|
|
|
protected virtual void OnDestroy()
|
2025-08-21 13:10:17 +08:00
|
|
|
|
{
|
|
|
|
|
CleanupResources();
|
2025-08-22 00:33:25 +08:00
|
|
|
|
}
|
2025-08-21 13:10:17 +08:00
|
|
|
|
|
2025-08-22 00:33:25 +08:00
|
|
|
|
// -------------- 钩子方法(Hook):子类可重写添加逻辑 --------------
|
|
|
|
|
/// <summary>Start后触发</summary>
|
|
|
|
|
protected virtual void OnStart()
|
|
|
|
|
{
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|
2025-08-22 00:33:25 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>截图流程开始前触发</summary>
|
|
|
|
|
protected virtual void OnBeforeCapture()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>原始截图完成后触发</summary>
|
|
|
|
|
protected virtual void OnAfterCaptureOriginal()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>裁剪完成后触发</summary>
|
|
|
|
|
protected virtual void OnAfterCrop()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>保存完成后触发</summary>
|
|
|
|
|
protected virtual void OnAfterSave()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>整个截图流程完成后触发</summary>
|
|
|
|
|
protected virtual void OnCaptureComplete()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------- 字段保护 --------------
|
|
|
|
|
protected string fullPath;
|
|
|
|
|
public string FullPath => fullPath; // 保留路径访问
|
2025-08-21 13:10:17 +08:00
|
|
|
|
}
|