NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/PrefabRecorderTool.cs

419 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.Recorder;
using UnityEditor.Recorder.Input;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// 自动批量录制Prefab的工具
/// </summary>
public class PrefabRecorderTool : MonoBehaviour
{
[Header("录制设置")]
[Tooltip("需要录制的Prefab所在的文件夹路径(相对于Assets目录)")]
public string prefabFolderPath = "Assets/Art/VFX";
[Tooltip("每个Prefab录制的次数")]
public int recordCountPerPrefab = 3;
[Tooltip("每次录制的时长(秒)")]
public float recordDuration = 3f;
[Tooltip("Prefab实例化的位置")]
public Vector3 spawnPosition = Vector3.zero;
[Tooltip("Prefab实例化的旋转")]
public Vector3 spawnRotation = Vector3.zero;
[Tooltip("录制输出的根目录")]
public string outputRootFolder = "Recordings";
[Tooltip("自动开始录制")]
public bool autoStartOnPlay = false;
[Header("录制器设置")]
[Tooltip("使用现有的Recorder Controller设置(可选,留空则使用下方参数创建新设置)")]
public RecorderControllerSettings existingRecorderSettings;
[Tooltip("录制分辨率宽度(仅在不使用现有设置时生效)")]
public int recordWidth = 1920;
[Tooltip("录制分辨率高度(仅在不使用现有设置时生效)")]
public int recordHeight = 1080;
[Tooltip("帧率(仅在不使用现有设置时生效)")]
public int frameRate = 30;
private RecorderController m_RecorderController;
private RecorderControllerSettings m_ControllerSettings;
private List<GameObject> m_PrefabList = new List<GameObject>();
private GameObject m_CurrentInstance;
private bool m_IsRecording = false;
private int m_CurrentPrefabIndex = 0;
private int m_CurrentRecordIndex = 0;
void Start()
{
#if UNITY_EDITOR
if (autoStartOnPlay)
{
StartBatchRecording();
}
#else
Debug.LogError("PrefabRecorderTool只能在编辑器模式下运行!");
#endif
}
/// <summary>
/// 开始批量录制
/// </summary>
public void StartBatchRecording()
{
#if UNITY_EDITOR
// 加载所有Prefab
LoadPrefabs();
if (m_PrefabList.Count == 0)
{
Debug.LogError($"在路径 {prefabFolderPath} 中没有找到任何Prefab!");
return;
}
Debug.Log($"找到 {m_PrefabList.Count} 个Prefab,开始批量录制...");
// 初始化录制控制器
InitializeRecorder();
// 开始录制第一个Prefab
m_CurrentPrefabIndex = 0;
m_CurrentRecordIndex = 0;
StartCoroutine(RecordNextPrefab());
#endif
}
/// <summary>
/// 加载指定文件夹中的所有Prefab
/// </summary>
void LoadPrefabs()
{
#if UNITY_EDITOR
m_PrefabList.Clear();
if (!Directory.Exists(prefabFolderPath))
{
Debug.LogError($"路径不存在: {prefabFolderPath}");
return;
}
// 获取文件夹中所有的prefab文件
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { prefabFolderPath });
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
{
m_PrefabList.Add(prefab);
Debug.Log($"加载Prefab: {prefab.name}");
}
}
#endif
}
/// <summary>
/// 初始化录制器
/// </summary>
void InitializeRecorder()
{
#if UNITY_EDITOR
if (existingRecorderSettings != null)
{
// 使用现有的Recorder设置
Debug.Log("使用现有的Recorder设置");
m_ControllerSettings = existingRecorderSettings;
m_RecorderController = new RecorderController(m_ControllerSettings);
}
else
{
// 创建新的录制器控制器设置
Debug.Log("创建新的Recorder设置");
m_ControllerSettings = ScriptableObject.CreateInstance<RecorderControllerSettings>();
m_RecorderController = new RecorderController(m_ControllerSettings);
// 创建图像序列录制器设置
var imageRecorder = ScriptableObject.CreateInstance<ImageRecorderSettings>();
imageRecorder.name = "Image Sequence Recorder";
imageRecorder.Enabled = true;
// 设置输出格式
imageRecorder.OutputFormat = ImageRecorderSettings.ImageRecorderOutputFormat.PNG;
imageRecorder.CaptureAlpha = true; // 启用透明通道
// 设置输入源(Texture Sampling)
var renderTextureSampler = new RenderTextureSamplerSettings();
// 设置相机源为 MainCamera(使用反射访问internal字段)
var sourceField = typeof(RenderTextureSamplerSettings).GetField("source", BindingFlags.NonPublic | BindingFlags.Instance);
if (sourceField != null)
{
sourceField.SetValue(renderTextureSampler, ImageSource.MainCamera);
}
renderTextureSampler.CameraTag = "MainCamera";
// 设置渲染分辨率为 QHD (2560x2560, 1:1 宽高比)
renderTextureSampler.RenderWidth = 2560;
renderTextureSampler.RenderHeight = 2560;
// 设置输出分辨率为 QHD (2560x2560, 1:1 宽高比)
renderTextureSampler.OutputWidth = 2560;
renderTextureSampler.OutputHeight = 2560;
// 设置宽高比为 1:1(使用反射访问internal字段)
var aspectRatioField = typeof(RenderTextureSamplerSettings).GetField("outputAspectRatio", BindingFlags.NonPublic | BindingFlags.Instance);
if (aspectRatioField != null)
{
var aspectRatioType = aspectRatioField.FieldType;
var aspectRatio = System.Activator.CreateInstance(aspectRatioType);
// 设置 m_ImageAspect 字段为 1:1 (枚举值 6)
var imageAspectField = aspectRatioType.GetField("m_ImageAspect", BindingFlags.NonPublic | BindingFlags.Instance);
if (imageAspectField != null)
{
var imageAspectEnumType = imageAspectField.FieldType;
var x1_1Value = System.Enum.Parse(imageAspectEnumType, "x1_1");
imageAspectField.SetValue(aspectRatio, x1_1Value);
}
aspectRatioField.SetValue(renderTextureSampler, aspectRatio);
}
// 设置超采样为 X8
renderTextureSampler.SuperSampling = SuperSamplingCount.X8;
// 其他设置
renderTextureSampler.FlipFinalOutput = false;
imageRecorder.imageInputSettings = renderTextureSampler;
// 设置文件名生成器,使用帧序号
imageRecorder.FileNameGenerator.Root = OutputPath.Root.Absolute;
imageRecorder.FileNameGenerator.Leaf = ""; // 初始为空,后续会设置
// 使用通配符格式:<Take>是录制序号,<Frame>是帧序号
imageRecorder.FileNameGenerator.FileName = "Image Sequence_<Take>_<Frame>";
Debug.Log("FileNameGenerator配置完成,格式: Image Sequence_<Take>_<Frame>.png");
// 设置录制模式为帧率
m_ControllerSettings.SetRecordModeToManual();
m_ControllerSettings.FrameRate = frameRate;
// 添加录制器
m_ControllerSettings.AddRecorderSettings(imageRecorder);
}
// 注意:不在这里PrepareRecording,而是在每次录制前准备
Debug.Log("Recorder初始化完成");
#endif
}
/// <summary>
/// 重置RecorderController,确保每次录制都从第1帧开始
/// </summary>
void ResetRecorderController()
{
#if UNITY_EDITOR
// 确保之前的录制已停止
if (m_IsRecording && m_RecorderController != null)
{
m_RecorderController.StopRecording();
m_IsRecording = false;
}
// 重新创建RecorderController实例以完全重置状态
// 使用相同的设置,但会重置所有内部状态(包括帧计数器)
m_RecorderController = new RecorderController(m_ControllerSettings);
Debug.Log("RecorderController已重置,帧计数器已归零");
#endif
}
/// <summary>
/// 录制下一个Prefab
/// </summary>
IEnumerator RecordNextPrefab()
{
#if UNITY_EDITOR
// 检查是否完成所有录制
if (m_CurrentPrefabIndex >= m_PrefabList.Count)
{
Debug.Log("所有Prefab录制完成!");
yield break;
}
GameObject currentPrefab = m_PrefabList[m_CurrentPrefabIndex];
// 检查当前Prefab的录制次数
if (m_CurrentRecordIndex >= recordCountPerPrefab)
{
m_CurrentPrefabIndex++;
m_CurrentRecordIndex = 0;
// 递归调用处理下一个Prefab
StartCoroutine(RecordNextPrefab());
yield break;
}
// 实例化Prefab
if (m_CurrentInstance != null)
{
Destroy(m_CurrentInstance);
}
m_CurrentInstance = Instantiate(currentPrefab, spawnPosition, Quaternion.Euler(spawnRotation));
Debug.Log($"实例化 {currentPrefab.name},第 {m_CurrentRecordIndex + 1}/{recordCountPerPrefab} 次录制");
// 等待两帧让场景稳定
yield return null;
yield return null;
// 设置输出路径(在PrepareRecording之前)
// 格式: {文件夹}/{Prefab名称}_{序号}/
string outputFolder = Path.Combine(outputRootFolder, $"{currentPrefab.name}_{m_CurrentRecordIndex + 1:D2}");
// 确保输出文件夹存在
string absoluteOutputFolder = Path.IsPathRooted(outputFolder) ? outputFolder : Path.Combine(Application.dataPath, "..", outputFolder);
absoluteOutputFolder = Path.GetFullPath(absoluteOutputFolder);
if (!Directory.Exists(absoluteOutputFolder))
{
Directory.CreateDirectory(absoluteOutputFolder);
Debug.Log($"创建输出文件夹: {absoluteOutputFolder}");
}
// 设置输出目录(不包含文件名,FileNameGenerator会处理文件名)
UpdateRecorderOutputPath(absoluteOutputFolder);
// 🔑 关键修复:重置RecorderController以清零帧计数器
// 这样每次录制都从第1帧开始,避免帧序号累积
ResetRecorderController();
Debug.Log($"RecorderController重置完成");
// 准备录制(每次录制前都需要重新准备)
m_RecorderController.PrepareRecording();
Debug.Log($"准备录制: {absoluteOutputFolder}");
// 等待一帧确保准备完成
yield return null;
// 开始录制
m_RecorderController.StartRecording();
m_IsRecording = true;
Debug.Log($"开始录制: {absoluteOutputFolder}");
// 等待录制时长
float recordedTime = 0f;
while (recordedTime < recordDuration)
{
recordedTime += Time.deltaTime;
yield return null;
}
// 停止录制
m_RecorderController.StopRecording();
m_IsRecording = false;
Debug.Log($"录制完成: {absoluteOutputFolder}");
// 等待一小段时间让文件写入完成和RecorderController完全停止
yield return new WaitForSeconds(0.5f);
// 增加录制索引
m_CurrentRecordIndex++;
// 继续录制
StartCoroutine(RecordNextPrefab());
#endif
yield return null;
}
/// <summary>
/// 更新录制器输出路径
/// </summary>
void UpdateRecorderOutputPath(string directoryPath)
{
#if UNITY_EDITOR
var recorderSettingsList = m_RecorderController.Settings.RecorderSettings.ToList();
if (recorderSettingsList.Count > 0)
{
var settings = recorderSettingsList[0] as ImageRecorderSettings;
if (settings != null)
{
// 设置输出目录(使用FileNameGenerator的路径格式)
// Recorder会在这个目录下使用FileNameGenerator生成文件名
settings.FileNameGenerator.Root = OutputPath.Root.Absolute;
settings.FileNameGenerator.Leaf = directoryPath;
Debug.Log($"设置输出目录: {directoryPath}");
Debug.Log($"文件命名格式: {settings.FileNameGenerator.BuildAbsolutePath(null)}");
}
else
{
Debug.LogWarning("找不到ImageRecorderSettings,无法设置输出路径");
}
}
else
{
Debug.LogWarning("RecorderSettings列表为空");
}
#endif
}
void OnDestroy()
{
// 清理
if (m_CurrentInstance != null)
{
Destroy(m_CurrentInstance);
}
if (m_IsRecording && m_RecorderController != null)
{
m_RecorderController.StopRecording();
}
}
void OnApplicationQuit()
{
OnDestroy();
}
#if UNITY_EDITOR
// 在Inspector中添加按钮
[ContextMenu("开始批量录制")]
void StartRecordingFromMenu()
{
StartBatchRecording();
}
[ContextMenu("停止录制")]
void StopRecordingFromMenu()
{
if (m_IsRecording && m_RecorderController != null)
{
m_RecorderController.StopRecording();
m_IsRecording = false;
StopAllCoroutines();
Debug.Log("录制已停止");
}
}
#endif
}