using System.Collections.Generic; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Audio; [CustomEditor(typeof(CampToolNew))] public class CampToolNewEditor : Editor { CampToolNew Temp; Vector2 scrollPosition; private Vector2 outerScrollPosition; private Vector2[] innerScrollPositions; public override void OnInspectorGUI() { //DrawDefaultInspector(); Temp = (CampToolNew)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.PropertyField(serializedObject.FindProperty("defaultAudioClip")); 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); OnInspectorGUIStepsDatas(); EditorGUILayout.EndScrollView(); EditorGUILayout.PropertyField(serializedObject.FindProperty("targetLength"), true); if (GUILayout.Button("Apply")) { Play(); } EditorGUILayout.Space(); EditorGUILayout.EndVertical(); //间隔 EditorGUILayout.Space(10); EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("后续修改音效", EditorStyles.boldLabel); //EditorGUILayout.PropertyField(serializedObject.FindProperty("audioChangeDatas")); OnInspectorGUIAudioChangeDatas(); // 结束外部滚动视图 EditorGUILayout.EndScrollView(); if (GUILayout.Button("ChangeAudio")) { ChangeAudio(); } EditorGUILayout.EndVertical(); serializedObject.ApplyModifiedProperties(); } void OnSceneGUI() { } void OnInspectorGUIStepsDatas() { // 获取列表属性 SerializedProperty stepsProperty = serializedObject.FindProperty("steps"); // 计算列表元素的总高度,包括展开的子属性 float totalHeight = 0f; for (int i = 0; i < stepsProperty.arraySize; i++) { SerializedProperty element = stepsProperty.GetArrayElementAtIndex(i); totalHeight += EditorGUI.GetPropertyHeight(element, true) + EditorGUIUtility.standardVerticalSpacing; } // 设置 ScrollView 的高度,最小高度为 100,最大高度为 400 float scrollViewHeight = Mathf.Clamp(totalHeight, 100, 400); scrollViewHeight += 60; scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(scrollViewHeight)); // 获取列表属性 Event e = Event.current; if (e.type == EventType.DragUpdated || e.type == EventType.DragPerform) { Rect dropArea = new Rect(scrollPosition.x, scrollPosition.y, 300, scrollViewHeight); if (dropArea.Contains(e.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (e.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (var obj in DragAndDrop.objectReferences) { if (obj is AnimationClip) { stepsProperty.arraySize++; SerializedProperty newElement = stepsProperty.GetArrayElementAtIndex(stepsProperty.arraySize - 1); SerializedProperty ani = newElement.FindPropertyRelative("ani"); SerializedProperty audioClip = newElement.FindPropertyRelative("audioClip"); SerializedProperty timeAniOffset = newElement.FindPropertyRelative("timeAniOffset"); ani.objectReferenceValue = obj; audioClip.objectReferenceValue = Temp.defaultAudioClip; timeAniOffset.floatValue = 0.1f; } } } e.Use(); } } EditorGUILayout.PropertyField(stepsProperty); } void OnInspectorGUIAudioChangeDatas() { // 计算外部列表元素的总高度,包括展开的子属性 SerializedProperty stepsProperty = serializedObject.FindProperty("audioChangeDatas"); float totalOuterHeight = 0f; for (int i = 0; i < stepsProperty.arraySize; i++) { SerializedProperty element = stepsProperty.GetArrayElementAtIndex(i); totalOuterHeight += EditorGUI.GetPropertyHeight(element, true) + EditorGUIUtility.standardVerticalSpacing; } // 设置外部 ScrollView 的高度,最小高度为 100,最大高度为 400 float outerScrollViewHeight = Mathf.Clamp(totalOuterHeight, 100, 400); outerScrollViewHeight += 60; // 开始外部滚动视图 outerScrollPosition = EditorGUILayout.BeginScrollView(outerScrollPosition, GUILayout.Height(outerScrollViewHeight)); // 获取列表属性 Event e = Event.current; if (e.type == EventType.DragUpdated || e.type == EventType.DragPerform) { Rect dropArea = new Rect(outerScrollPosition.x, outerScrollPosition.y, 300, outerScrollViewHeight); if (dropArea.Contains(e.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (e.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); if (stepsProperty.arraySize == 0) { stepsProperty.arraySize++; } SerializedProperty audioChangeDatasSubRoot = stepsProperty.GetArrayElementAtIndex(stepsProperty.arraySize - 1); SerializedProperty audioChangeDatasSub = audioChangeDatasSubRoot.FindPropertyRelative("audioChangeDatas"); foreach (var obj in DragAndDrop.objectReferences) { if (obj is AnimationClip) { //AnimationClip clip = (AnimationClip)obj; Debug.Log("AnimationClip :" + obj.name); audioChangeDatasSub.arraySize++; SerializedProperty newElement = audioChangeDatasSub.GetArrayElementAtIndex(audioChangeDatasSub.arraySize - 1); SerializedProperty ani = newElement.FindPropertyRelative("ani"); SerializedProperty audioClip = newElement.FindPropertyRelative("audioClip"); ani.objectReferenceValue = obj; audioClip.objectReferenceValue = Temp.defaultAudioClip; } } } e.Use(); } } EditorGUILayout.PropertyField(stepsProperty); } 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(); } private string _mixerPath = "Assets/ABPackage/pkgFirstPackage/Audio/AudioMixer.mixer"; 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; AudioMixer mixer = AssetDatabase.LoadAssetAtPath(_mixerPath); 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; aud.outputAudioMixerGroup = mixer.FindMatchingGroups("Fishing")[0]; 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++) { AudioChangeDataRoot dataRoot = Temp.audioChangeDatas[n]; var audioChangeDatas = dataRoot.audioChangeDatas; for (int i = 0; i < audioChangeDatas.Length; i++) { try { AudioChangeData data = audioChangeDatas[i]; 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); } catch (System.Exception e) { Debug.LogError($"Error processing audio change data at index {i} in root '{dataRoot.name}': {e.Message}"); return; } } } 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."); } }