using System.Collections.Generic; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; [CustomEditor(typeof(CampTool))] public class CampToolEditor : Editor { CampTool Temp; public override void OnInspectorGUI() { //DrawDefaultInspector(); Temp = (CampTool)target; // 显示文件夹路径 EditorGUILayout.LabelField("保存路径: " + Temp.folderPath); if (GUILayout.Button("选择")) { // Temp.folderPath = EditorUtility.OpenFolderPanel("选择文件夹", Temp.folderPath, ""); var absPath = EditorUtility.OpenFolderPanel("选择文件夹", Temp.folderPath, ""); Temp.folderPath = absPath.Substring(absPath.IndexOf("Assets/")); EditorUtility.SetDirty(Temp); EditorSceneManager.SaveOpenScenes(); } EditorGUILayout.PropertyField(serializedObject.FindProperty("dynamic")); EditorGUILayout.BeginVertical("box"); EditorGUILayout.Space(); GUIStyle labelStyle = new GUIStyle(GUI.skin.label); labelStyle.normal.textColor = Color.red; // Set the color you want labelStyle.fontStyle = FontStyle.BoldAndItalic; labelStyle.fontSize = 20; labelStyle.wordWrap = true; string labelText = "模版动画中每一个轨道,在动画最后一帧必须有关键帧,因为拿每一条轨道的最后一帧时间播放音效"; float labelHeight = labelStyle.CalcHeight(new GUIContent(labelText), EditorGUIUtility.currentViewWidth - 40); EditorGUILayout.LabelField(labelText, labelStyle, GUILayout.Height(labelHeight)); EditorGUILayout.LabelField("生成初始动画", EditorStyles.boldLabel); EditorGUILayout.PropertyField(serializedObject.FindProperty("steps")); EditorGUILayout.PropertyField(serializedObject.FindProperty("targetLength")); if (GUILayout.Button("Apply")) { Play(); } EditorGUILayout.Space(); EditorGUILayout.EndVertical(); //间隔 EditorGUILayout.Space(10); EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("后续修改音效", EditorStyles.boldLabel); EditorGUILayout.PropertyField(serializedObject.FindProperty("audioChangeDatas")); if (GUILayout.Button("ChangeAudio")) { ChangeAudio(); } EditorGUILayout.EndVertical(); serializedObject.ApplyModifiedProperties(); } public void Play() { for (int n = 0; n < Temp.steps.Length; n++) { StepRootData step = Temp.steps[n]; Transform root2 = step.StepRoot; Transform root1 = root2.parent; string path1 = root1.name; string path2 = root2.name; //创建新的AnimationClip AnimationClip newAni = new AnimationClip(); if (string.IsNullOrEmpty(step.newAniName)) { newAni.name = $"{path1}_{path2}"; } else { newAni.name = step.newAniName; } List timeList = new List(); EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(step.ani); int count = root2.childCount; float bindingTime = 0f; for (int j = 0; j < bindings.Length; j++) { var binding = bindings[j]; AnimationCurve curve = AnimationUtility.GetEditorCurve(step.ani, binding); // 获取关键帧的数量 int keyCount = curve.keys.Length; // 移动关键帧的位置 for (int k = keyCount - 1; k >= 0; k--) { Keyframe key = curve.keys[k]; key.time += 0.02f; if (bindingTime < key.time) { bindingTime = key.time; } curve.MoveKey(k, key); } for (int i = 0; i < count; i++) { // string trackName = $"{path1}/{path2}/{i + 1}/AnimRoot"; string trackName = $"{path1}/{path2}/{i + 1:D2}/AnimRoot"; EditorCurveBinding trackBinding = new EditorCurveBinding { type = binding.type, path = trackName, propertyName = binding.propertyName }; // 将轨道添加到Clip中 AnimationUtility.SetEditorCurve(newAni, trackBinding, curve); if (j == 0 && step.audioClip != null) { int keyTime = Mathf.RoundToInt(bindingTime * 10000); if (!timeList.Contains(keyTime)) { timeList.Add(keyTime); } } // 移动关键帧的位置 for (int k = keyCount - 1; k >= 0; k--) { Keyframe key = curve.keys[k]; key.time += step.timeAniOffset; if (bindingTime < key.time) { bindingTime = key.time; } curve.MoveKey(k, key); } } } if (step.audioClip != null) { SetBindingAudio(timeList, newAni, step.audioClip); } if (Temp.targetLength > 0) { // Get the original length of the clip float originalLength = newAni.length; if (originalLength <= 0) { Debug.LogWarning($"Clip '{newAni.name}' has no length to scale."); continue; } // Calculate the new speed to scale the animation float scaleFactor = originalLength / Temp.targetLength; // Scale the animation clip speed by adjusting its frame rates ScaleClip(newAni, scaleFactor); } AssetDatabase.CreateAsset(newAni, $"{Temp.folderPath}/{newAni.name}.anim"); } //保存 Temp AssetDatabase.SaveAssets(); } void SetBindingAudio(List timeList, AnimationClip newAni, AudioClip audioClip) { List bindingAudio = new List(); List curveAudio = new List(); int audioIndex = 0; Keyframe keyframe = new Keyframe(0, 0, Mathf.Infinity, Mathf.Infinity); string AudioRoot = "Audio"; Transform AudioTran = Temp.dynamic.Find(AudioRoot); if (AudioTran == null) { GameObject go = new GameObject(AudioRoot); go.transform.SetParent(Temp.dynamic); AudioTran = go.transform; } int maxTime = 0; for (int j = 0; j < timeList.Count; j++) { audioIndex %= 3; Transform tran2 = AudioTran.Find($"{newAni.name}_{audioIndex}"); AudioSource aud; if (tran2) { aud = tran2.GetComponent(); } else { var go = new GameObject($"{newAni.name}_{audioIndex}", typeof(AudioSource)); go.transform.SetParent(AudioTran); aud = go.GetComponent(); go.SetActive(false); } aud.clip = audioClip; if (bindingAudio.Count <= audioIndex) { bindingAudio.Add(new EditorCurveBinding { type = typeof(GameObject), path = $"{AudioRoot}/{newAni.name}_{audioIndex}", propertyName = "m_IsActive" }); } if (curveAudio.Count <= audioIndex) { curveAudio.Add(new AnimationCurve()); curveAudio[audioIndex].AddKey(keyframe); } if (maxTime < timeList[j]) { maxTime = timeList[j]; } float time = timeList[j] / 10000f; Keyframe key2 = new Keyframe(time - 0.02f, 0, Mathf.Infinity, Mathf.Infinity); Keyframe key3 = new Keyframe(time, 1, Mathf.Infinity, Mathf.Infinity); curveAudio[audioIndex].AddKey(key2); curveAudio[audioIndex].AddKey(key3); audioIndex++; } for (int i = 0; i < bindingAudio.Count; i++) { if (newAni == null || bindingAudio[i] == null || curveAudio[i] == null) continue; float time = maxTime / 10000f; Keyframe key = new Keyframe(time, 0); curveAudio[i].AddKey(key); int lastKey = curveAudio[i].length - 1; curveAudio[i].MoveKey(lastKey, key); AnimationUtility.SetEditorCurve(newAni, bindingAudio[i], curveAudio[i]); } } public void ChangeAudio() { for (int n = 0; n < Temp.audioChangeDatas.Length; n++) { AudioChangeData data = Temp.audioChangeDatas[n]; AnimationClip newAni = data.ani; EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(data.ani); List timeList = new List(); for (int j = 0; j < bindings.Length; j++) { var binding = bindings[j]; if (binding.path.Contains($"Audio/{newAni.name}")) { continue; } AnimationCurve curve = AnimationUtility.GetEditorCurve(data.ani, binding); // 获取关键帧的数量 int keyCount = curve.keys.Length; Keyframe key = curve.keys[keyCount - 1]; int keyTime = Mathf.RoundToInt(key.time * 10000); if (!timeList.Contains(keyTime)) { timeList.Add(keyTime); } } SetBindingAudio(timeList, newAni, data.audioClip); EditorUtility.SetDirty(data.ani); } AssetDatabase.SaveAssets(); } private void ScaleClip(AnimationClip clip, float scaleFactor) { // Debug.Log($"{scaleFactor} for {clip}"); // Scale all curves in the clip foreach (var binding in AnimationUtility.GetCurveBindings(clip)) { var curve = AnimationUtility.GetEditorCurve(clip, binding); var curveKeyFrames = curve.keys; // Adjust keyframe times for (int i = 0; i < curveKeyFrames.Length; i++) { // Debug.Log($" before: {curveKeyFrames[i].time}"); // var t = curveKeyFrames[i].time / scaleFactor; // curve.keys[i].time = t; curveKeyFrames[i].time /= scaleFactor; // Debug.Log($" after: {curveKeyFrames[i].time}"); // Debug.Log($" after t: {t}"); } curve.keys = curveKeyFrames; AnimationUtility.SetEditorCurve(clip, binding, curve); } foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) { var curve = AnimationUtility.GetEditorCurve(clip, binding); for (int i = 0; i < curve.keys.Length; i++) { curve.keys[i].time /= scaleFactor; } AnimationUtility.SetEditorCurve(clip, binding, curve); } // Scale events foreach (var animEvent in AnimationUtility.GetAnimationEvents(clip)) { animEvent.time /= scaleFactor; } // Apply changes back to the clip AnimationUtility.SetAnimationEvents(clip, AnimationUtility.GetAnimationEvents(clip)); Debug.Log($"Scaled clip '{clip.name}' to {clip.length / scaleFactor} seconds."); } }