diff --git a/Assets/Editor/Window/AnimationClipGeneratorCore.cs b/Assets/Editor/Window/AnimationClipGeneratorCore.cs index afc7d1ca1..fbc842630 100644 --- a/Assets/Editor/Window/AnimationClipGeneratorCore.cs +++ b/Assets/Editor/Window/AnimationClipGeneratorCore.cs @@ -433,7 +433,7 @@ namespace AibisDream.SystemEditor AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate); } - // 创建AnimationClip + // 创建或更新 AnimationClip(若已存在则在原资源上就地更新,避免引用丢失) public static AnimationClip CreateAnimationClip( ProcessedAnimationClip animationData, List frames, @@ -441,7 +441,28 @@ namespace AibisDream.SystemEditor string outputPath, bool isImageAnimation = false) { - AnimationClip clip = new AnimationClip(); + // 先计算目标资源路径 + string fullPath = Path.Combine(outputPath, $"{animationData.name}.anim"); + string assetPath = fullPath.Replace('\\', '/'); + if (!assetPath.StartsWith("Assets/")) + { + // 转换为相对路径 + string projectPath = Application.dataPath.Replace("Assets", ""); + if (assetPath.StartsWith(projectPath)) + { + assetPath = assetPath.Substring(projectPath.Length); + } + } + + // 如果已经存在同名 AnimationClip,则复用该资源,在其上就地更新 + AnimationClip clip = AssetDatabase.LoadAssetAtPath(assetPath); + bool isNewClip = false; + if (clip == null) + { + clip = new AnimationClip(); + isNewClip = true; + } + clip.name = animationData.name; // 设置采样率 @@ -512,20 +533,18 @@ namespace AibisDream.SystemEditor settings.loopTime = animationData.loop; AnimationUtility.SetAnimationClipSettings(clip, settings); - // 保存AnimationClip - string fullPath = Path.Combine(outputPath, $"{animationData.name}.anim"); - string assetPath = fullPath.Replace('\\', '/'); - if (!assetPath.StartsWith("Assets/")) + // 保存 / 更新 AnimationClip 资源 + if (isNewClip) { - // 转换为相对路径 - string projectPath = Application.dataPath.Replace("Assets", ""); - if (assetPath.StartsWith(projectPath)) - { - assetPath = assetPath.Substring(projectPath.Length); - } + // 不存在则创建新资源 + AssetDatabase.CreateAsset(clip, assetPath); + } + else + { + // 已存在则标记为已修改,保持原 GUID 与引用 + EditorUtility.SetDirty(clip); } - AssetDatabase.CreateAsset(clip, assetPath); AssetDatabase.SaveAssets(); return clip; diff --git a/Assets/Editor/Window/AnimatorBlendTreeParserWindow.cs b/Assets/Editor/Window/AnimatorBlendTreeParserWindow.cs index 8e4132d53..5dec3a523 100644 --- a/Assets/Editor/Window/AnimatorBlendTreeParserWindow.cs +++ b/Assets/Editor/Window/AnimatorBlendTreeParserWindow.cs @@ -15,6 +15,8 @@ namespace AibisDream.SystemEditor private List _blendTreeInfos; private Vector2 _scrollPosition; private string _resultText = ""; + private bool _includeNested = false; // 是否包含嵌套混合树,默认不提取 + private string _defaultStateName = ""; // 控制器默认 State 名称 [MenuItem("Tools/Animator BlendTree Parser")] public static void ShowWindow() @@ -33,6 +35,17 @@ namespace AibisDream.SystemEditor EditorGUILayout.Space(10); + // 解析选项 + EditorGUILayout.LabelField("解析选项", EditorStyles.boldLabel); + _includeNested = EditorGUILayout.Toggle("包含嵌套混合树", _includeNested); + EditorGUILayout.HelpBox( + _includeNested + ? "将提取所有层级的混合树(包括嵌套在子混合树中的混合树)" + : "仅提取第一层混合树(不包含嵌套在子混合树中的混合树)", + MessageType.Info); + + EditorGUILayout.Space(10); + // 解析按钮 EditorGUI.BeginDisabledGroup(_selectedController == null); if (GUILayout.Button("解析混合树", GUILayout.Height(30))) @@ -84,13 +97,27 @@ namespace AibisDream.SystemEditor try { - // 调用解析方法(不输出到控制台,只返回结果) - _blendTreeInfos = AnimatorKit.ParseBlendTree(_selectedController); + // 解析默认 State 名称(默认使用第 0 层的默认状态) + if (_selectedController.layers != null && _selectedController.layers.Length > 0) + { + var baseLayer = _selectedController.layers[0]; + _defaultStateName = baseLayer.stateMachine?.defaultState != null + ? baseLayer.stateMachine.defaultState.name + : string.Empty; + } + else + { + _defaultStateName = string.Empty; + } + + // 调用解析方法,传入 includeNested 选项 + _blendTreeInfos = AnimatorKit.ParseBlendTree(_selectedController, _includeNested); // 格式化结果显示文本 FormatResultText(); - Debug.Log($"解析完成: 找到 {_blendTreeInfos.Count} 个混合树"); + Debug.Log( + $"解析完成: 找到 {_blendTreeInfos.Count} 个混合树 (包含嵌套: {_includeNested}),默认 State: {_defaultStateName}"); } catch (Exception e) { @@ -111,6 +138,7 @@ namespace AibisDream.SystemEditor var lines = new List(); lines.Add($"=== 控制器 {_selectedController.name} 的混合树信息 ==="); + lines.Add($"默认 State: {_defaultStateName}"); lines.Add($"共找到 {_blendTreeInfos.Count} 个混合树\n"); for (int i = 0; i < _blendTreeInfos.Count; i++) @@ -211,8 +239,15 @@ namespace AibisDream.SystemEditor Formatting = Formatting.Indented }; + // 组装配置对象(包含默认 State 名称和混合树列表) + var config = new BlendTreeConfig + { + defaultStateName = _defaultStateName, + blendTrees = _blendTreeInfos + }; + // 序列化为 JSON - string jsonContent = JsonConvert.SerializeObject(_blendTreeInfos, settings); + string jsonContent = JsonConvert.SerializeObject(config, settings); // 写入文件(如果已存在则覆盖) File.WriteAllText(jsonPath, jsonContent, System.Text.Encoding.UTF8); diff --git a/Assets/RawResources/动画测试/动作序列/俯身动作.anim b/Assets/RawResources/动画测试/动作序列/俯身动作.anim index 78447350d..102c87966 100644 --- a/Assets/RawResources/动画测试/动作序列/俯身动作.anim +++ b/Assets/RawResources/动画测试/动作序列/俯身动作.anim @@ -21,17 +21,17 @@ AnimationClip: - serializedVersion: 2 curve: - time: 0 - value: {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.3 - value: {fileID: 4571778429836060549, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.5 - value: {fileID: 5925744834341221605, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.55 - value: {fileID: 3417067792297643795, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.75 - value: {fileID: 8029721251602746815, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 1.15 - value: {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + value: {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.4 + value: {fileID: -6190755443841108145, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.6 + value: {fileID: -8109228470294260414, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.65000004 + value: {fileID: 6617401414338900246, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.85 + value: {fileID: 245859675350752340, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 1.1500001 + value: {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} attribute: m_Sprite path: classID: 212 @@ -54,12 +54,12 @@ AnimationClip: isIntCurve: 0 isSerializeReferenceCurve: 0 pptrCurveMapping: - - {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 4571778429836060549, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 5925744834341221605, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 3417067792297643795, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 8029721251602746815, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: -6190755443841108145, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: -8109228470294260414, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 6617401414338900246, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 245859675350752340, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} m_AnimationClipSettings: serializedVersion: 2 m_AdditiveReferencePoseClip: {fileID: 0} diff --git a/Assets/RawResources/动画测试/动作序列/说话到俯身.json b/Assets/RawResources/动画测试/动作序列/说话到俯身.json index 3661460e1..0d3c99d1c 100644 --- a/Assets/RawResources/动画测试/动作序列/说话到俯身.json +++ b/Assets/RawResources/动画测试/动作序列/说话到俯身.json @@ -88,8 +88,8 @@ "size": { "w": 444, "h": 300 }, "scale": "1", "frameTags": [ - { "name": "俯身动作", "from": 0, "to": 4, "direction": "forward" }, - { "name": "起身动作", "from": 5, "to": 9, "direction": "forward" } + { "name": "起身动作", "from": 0, "to": 4, "direction": "forward" }, + { "name": "俯身动作", "from": 5, "to": 9, "direction": "forward" } ], "layers": [ { "name": "调酒妹", "opacity": 255, "blendMode": "normal" } diff --git a/Assets/RawResources/动画测试/动作序列/起身动作.anim b/Assets/RawResources/动画测试/动作序列/起身动作.anim index 62593d7f2..1e7c48791 100644 --- a/Assets/RawResources/动画测试/动作序列/起身动作.anim +++ b/Assets/RawResources/动画测试/动作序列/起身动作.anim @@ -21,17 +21,17 @@ AnimationClip: - serializedVersion: 2 curve: - time: 0 - value: {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.4 - value: {fileID: -6190755443841108145, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.6 - value: {fileID: -8109228470294260414, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.65000004 - value: {fileID: 6617401414338900246, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 0.85 - value: {fileID: 245859675350752340, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - time: 1.1500001 - value: {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + value: {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.3 + value: {fileID: 4571778429836060549, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.5 + value: {fileID: 5925744834341221605, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.55 + value: {fileID: 3417067792297643795, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 0.75 + value: {fileID: 8029721251602746815, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - time: 1.15 + value: {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} attribute: m_Sprite path: classID: 212 @@ -54,12 +54,12 @@ AnimationClip: isIntCurve: 0 isSerializeReferenceCurve: 0 pptrCurveMapping: - - {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: -6190755443841108145, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: -8109228470294260414, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 6617401414338900246, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 245859675350752340, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} - - {fileID: 7719310255684422536, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 4571778429836060549, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 5925744834341221605, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 3417067792297643795, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: 8029721251602746815, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} + - {fileID: -8686660269677207458, guid: 25ade8fcabe12234e947eb2fd1faf797, type: 3} m_AnimationClipSettings: serializedVersion: 2 m_AdditiveReferencePoseClip: {fileID: 0} diff --git a/Assets/RawResources/动画测试/调酒妹测试.controller b/Assets/RawResources/动画测试/调酒妹测试.controller index bb0b2b056..31e9c9e42 100644 --- a/Assets/RawResources/动画测试/调酒妹测试.controller +++ b/Assets/RawResources/动画测试/调酒妹测试.controller @@ -15,6 +15,50 @@ BlendTree: m_UseAutomaticThresholds: 1 m_NormalizedBlendValues: 0 m_BlendType: 0 +--- !u!1101 &-8756764537236975013 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 4025084367579902075} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &-8393837749183813681 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1585003012359330868} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!206 &-8129769959329347800 BlendTree: m_ObjectHideFlags: 1 @@ -30,31 +74,6 @@ BlendTree: m_UseAutomaticThresholds: 1 m_NormalizedBlendValues: 0 m_BlendType: 0 ---- !u!1101 &-7332683429446511761 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: "\u7AD9\u7ACB" - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 2000764964157969243} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 --- !u!206 &-7090401831593733310 BlendTree: m_ObjectHideFlags: 1 @@ -70,6 +89,31 @@ BlendTree: m_UseAutomaticThresholds: 1 m_NormalizedBlendValues: 0 m_BlendType: 0 +--- !u!1101 &-6924652291709804553 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: "\u4FEF\u8EAB" + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 7455082539712181259} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!206 &-5506951507918232734 BlendTree: m_ObjectHideFlags: 1 @@ -322,6 +366,31 @@ BlendTree: m_UseAutomaticThresholds: 1 m_NormalizedBlendValues: 0 m_BlendType: 0 +--- !u!1101 &-1840047729121099618 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: "\u7AD9\u7ACB" + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 2000764964157969243} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1102 &-1585003012359330868 AnimatorState: serializedVersion: 6 @@ -333,7 +402,7 @@ AnimatorState: m_Speed: 1 m_CycleOffset: 0 m_Transitions: - - {fileID: 7615406609444337958} + - {fileID: -6924652291709804553} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -349,28 +418,6 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: ---- !u!1101 &-1160214663374449175 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: [] - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 4025084367579902075} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 --- !u!206 &-1010760190615495790 BlendTree: m_ObjectHideFlags: 1 @@ -557,25 +604,25 @@ AnimatorController: m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 9100000} + m_Controller: {fileID: 0} - m_Name: Is Talking m_Type: 1 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 9100000} + m_Controller: {fileID: 0} - m_Name: "\u7AD9\u7ACB" m_Type: 9 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 9100000} + m_Controller: {fileID: 0} - m_Name: "\u4FEF\u8EAB" m_Type: 9 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 - m_Controller: {fileID: 9100000} + m_Controller: {fileID: 0} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer @@ -670,7 +717,7 @@ AnimatorState: m_Speed: 1 m_CycleOffset: 0 m_Transitions: - - {fileID: 6437050022299151312} + - {fileID: -8393837749183813681} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -693,11 +740,11 @@ AnimatorState: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: "\u4FEF\u8EAB2D\u6DF7\u5408" + m_Name: "\u4FEF\u8EAB" m_Speed: 1 m_CycleOffset: 0 m_Transitions: - - {fileID: -7332683429446511761} + - {fileID: -1840047729121099618} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -744,28 +791,6 @@ BlendTree: m_UseAutomaticThresholds: 1 m_NormalizedBlendValues: 0 m_BlendType: 0 ---- !u!1101 &6437050022299151312 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: [] - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: -1585003012359330868} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 --- !u!1107 &6533160500192696812 AnimatorStateMachine: serializedVersion: 6 @@ -777,16 +802,16 @@ AnimatorStateMachine: m_ChildStates: - serializedVersion: 1 m_State: {fileID: -1585003012359330868} - m_Position: {x: 400, y: 140, z: 0} + m_Position: {x: 390, y: 120, z: 0} - serializedVersion: 1 m_State: {fileID: 4025084367579902075} - m_Position: {x: 400, y: 580, z: 0} + m_Position: {x: 390, y: 560, z: 0} - serializedVersion: 1 m_State: {fileID: 7455082539712181259} - m_Position: {x: 400, y: 330, z: 0} + m_Position: {x: 390, y: 310, z: 0} - serializedVersion: 1 m_State: {fileID: 2000764964157969243} - m_Position: {x: 400, y: 380, z: 0} + m_Position: {x: 390, y: 360, z: 0} - serializedVersion: 1 m_State: {fileID: -4513199141124557387} m_Position: {x: 800, y: 580, z: 0} @@ -826,7 +851,7 @@ AnimatorState: m_Speed: 1 m_CycleOffset: 0 m_Transitions: - - {fileID: -1160214663374449175} + - {fileID: -8756764537236975013} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -842,31 +867,6 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: ---- !u!1101 &7615406609444337958 -AnimatorStateTransition: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: "\u4FEF\u8EAB" - m_EventTreshold: 0 - m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 7455082539712181259} - m_Solo: 0 - m_Mute: 0 - m_IsExit: 0 - serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.75 - m_HasExitTime: 1 - m_HasFixedDuration: 1 - m_InterruptionSource: 0 - m_OrderedInterruption: 1 - m_CanTransitionToSelf: 1 --- !u!206 &7733507570337176853 BlendTree: m_ObjectHideFlags: 1 diff --git a/Assets/RawResources/动画测试/调酒妹测试.json b/Assets/RawResources/动画测试/调酒妹测试.json index e439d572f..4bab66679 100644 --- a/Assets/RawResources/动画测试/调酒妹测试.json +++ b/Assets/RawResources/动画测试/调酒妹测试.json @@ -1,462 +1,310 @@ -[ - { - "name": "站立", - "type": "1D", - "clipCount": 8, - "clips": [ - { - "clipName": "IDLE", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 +{ + "defaultStateName": "站立", + "blendTrees": [ + { + "name": "站立", + "type": "1D", + "clipCount": 8, + "clips": [ + { + "clipName": "IDLE", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.0 }, - "threshold": 0.0 - }, - { - "clipName": "1 伤心聆听", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 伤心聆听", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.142857149 }, - "threshold": 0.142857149 - }, - { - "clipName": "1 强颜欢笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 强颜欢笑", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.2857143 }, - "threshold": 0.2857143 - }, - { - "clipName": "1 眯眼笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 眯眼笑", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.428571433 }, - "threshold": 0.428571433 - }, - { - "clipName": "1 聆听", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 聆听", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.5714286 }, - "threshold": 0.5714286 - }, - { - "clipName": "1 苦笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 苦笑", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.714285731 }, - "threshold": 0.714285731 - }, - { - "clipName": "1 闭眼笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 闭眼笑", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.857142866 }, - "threshold": 0.857142866 - }, - { - "clipName": "1 难过", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "1 难过", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 1.0 + } + ], + "minThreshold": 0.0, + "maxThreshold": 1.0, + "blendParameter": "Face Param", + "blendParameterY": "Blend" + }, + { + "name": "俯身", + "type": "2D", + "clipCount": 0, + "clips": [ + { + "clipName": "2 俯身A", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 + }, + "threshold": 0.0 }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Face Param", - "blendParameterY": "Blend" - }, - { - "name": "俯身", - "type": "2D", - "clipCount": 0, - "clips": [ - { - "clipName": "2 俯身A", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 + { + "clipName": "2 俯身B", + "position": { + "x": 0.25, + "y": 0.0, + "normalized": { + "x": 1.0, + "y": 0.0, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "magnitude": 0.25, + "sqrMagnitude": 0.0625 + }, + "threshold": 0.0 }, - "threshold": 0.0 - }, - { - "clipName": "2 俯身B", - "position": { - "x": 0.25, - "y": 0.0, - "normalized": { + { + "clipName": "2 微笑", + "position": { + "x": 0.5, + "y": 0.0, + "normalized": { + "x": 1.0, + "y": 0.0, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "magnitude": 0.5, + "sqrMagnitude": 0.25 + }, + "threshold": 0.0 + }, + { + "clipName": "2 眯眼A", + "position": { + "x": 0.75, + "y": 0.0, + "normalized": { + "x": 1.0, + "y": 0.0, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "magnitude": 0.75, + "sqrMagnitude": 0.5625 + }, + "threshold": 0.0 + }, + { + "clipName": "2 眯眼B", + "position": { "x": 1.0, "y": 0.0, "magnitude": 1.0, "sqrMagnitude": 1.0 }, - "magnitude": 0.25, - "sqrMagnitude": 0.0625 + "threshold": 0.0 }, - "threshold": 0.0 - }, - { - "clipName": "2 微笑", - "position": { - "x": 0.5, - "y": 0.0, - "normalized": { + { + "clipName": "2 俯身谈话", + "position": { + "x": 0.0, + "y": 1.0, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "threshold": 0.0 + }, + { + "clipName": "2 俯身谈话", + "position": { + "x": 0.25, + "y": 1.0, + "normalized": { + "x": 0.242535636, + "y": 0.970142543, + "magnitude": 1.0, + "sqrMagnitude": 1.00000012 + }, + "magnitude": 1.03077638, + "sqrMagnitude": 1.0625 + }, + "threshold": 0.0 + }, + { + "clipName": "2 微笑对话", + "position": { + "x": 0.5, + "y": 1.0, + "normalized": { + "x": 0.4472136, + "y": 0.8944272, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "magnitude": 1.118034, + "sqrMagnitude": 1.25 + }, + "threshold": 0.0 + }, + { + "clipName": "2 眯眼对话", + "position": { + "x": 0.75, + "y": 1.0, + "normalized": { + "x": 0.6, + "y": 0.8, + "magnitude": 1.0, + "sqrMagnitude": 1.0 + }, + "magnitude": 1.25, + "sqrMagnitude": 1.5625 + }, + "threshold": 0.0 + }, + { + "clipName": "2 眯眼对话", + "position": { "x": 1.0, + "y": 1.0, + "normalized": { + "x": 0.707106769, + "y": 0.707106769, + "magnitude": 1.0, + "sqrMagnitude": 0.99999994 + }, + "magnitude": 1.41421354, + "sqrMagnitude": 2.0 + }, + "threshold": 0.0 + } + ], + "minThreshold": 0.0, + "maxThreshold": 1.0, + "blendParameter": "Face Param", + "blendParameterY": "Is Talking" + }, + { + "name": "俯身嵌套", + "type": "1D", + "clipCount": 5, + "clips": [ + { + "clipName": "俯身A", + "position": { + "x": 0.0, "y": 0.0, - "magnitude": 1.0, - "sqrMagnitude": 1.0 + "magnitude": 0.0, + "sqrMagnitude": 0.0 }, - "magnitude": 0.5, - "sqrMagnitude": 0.25 + "threshold": 0.0 }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼A", - "position": { - "x": 0.75, - "y": 0.0, - "normalized": { - "x": 1.0, + { + "clipName": "俯身B", + "position": { + "x": 0.0, "y": 0.0, - "magnitude": 1.0, - "sqrMagnitude": 1.0 + "magnitude": 0.0, + "sqrMagnitude": 0.0 }, - "magnitude": 0.75, - "sqrMagnitude": 0.5625 + "threshold": 0.25 }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼B", - "position": { - "x": 1.0, - "y": 0.0, - "magnitude": 1.0, - "sqrMagnitude": 1.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 俯身谈话", - "position": { - "x": 0.0, - "y": 1.0, - "magnitude": 1.0, - "sqrMagnitude": 1.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 俯身谈话", - "position": { - "x": 0.25, - "y": 1.0, - "normalized": { - "x": 0.242535636, - "y": 0.970142543, - "magnitude": 1.0, - "sqrMagnitude": 1.00000012 + { + "clipName": "微笑", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 }, - "magnitude": 1.03077638, - "sqrMagnitude": 1.0625 + "threshold": 0.5 }, - "threshold": 0.0 - }, - { - "clipName": "2 微笑对话", - "position": { - "x": 0.5, - "y": 1.0, - "normalized": { - "x": 0.4472136, - "y": 0.8944272, - "magnitude": 1.0, - "sqrMagnitude": 1.0 + { + "clipName": "眯眼A", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 }, - "magnitude": 1.118034, - "sqrMagnitude": 1.25 + "threshold": 0.75 }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼对话", - "position": { - "x": 0.75, - "y": 1.0, - "normalized": { - "x": 0.6, - "y": 0.8, - "magnitude": 1.0, - "sqrMagnitude": 1.0 + { + "clipName": "眯眼B", + "position": { + "x": 0.0, + "y": 0.0, + "magnitude": 0.0, + "sqrMagnitude": 0.0 }, - "magnitude": 1.25, - "sqrMagnitude": 1.5625 - }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼对话", - "position": { - "x": 1.0, - "y": 1.0, - "normalized": { - "x": 0.707106769, - "y": 0.707106769, - "magnitude": 1.0, - "sqrMagnitude": 0.99999994 - }, - "magnitude": 1.41421354, - "sqrMagnitude": 2.0 - }, - "threshold": 0.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Face Param", - "blendParameterY": "Is Talking" - }, - { - "name": "俯身A", - "type": "1D", - "clipCount": 2, - "clips": [ - { - "clipName": "2 俯身A", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 俯身谈话", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Is Talking", - "blendParameterY": "Blend" - }, - { - "name": "俯身B", - "type": "1D", - "clipCount": 2, - "clips": [ - { - "clipName": "2 俯身B", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 俯身谈话", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Is Talking", - "blendParameterY": "Blend" - }, - { - "name": "微笑", - "type": "1D", - "clipCount": 2, - "clips": [ - { - "clipName": "2 微笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 微笑对话", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Is Talking", - "blendParameterY": "Blend" - }, - { - "name": "眯眼A", - "type": "1D", - "clipCount": 2, - "clips": [ - { - "clipName": "2 眯眼A", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼对话", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Is Talking", - "blendParameterY": "Blend" - }, - { - "name": "眯眼B", - "type": "1D", - "clipCount": 2, - "clips": [ - { - "clipName": "2 眯眼B", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "2 眯眼对话", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Is Talking", - "blendParameterY": "Blend" - }, - { - "name": "俯身嵌套", - "type": "1D", - "clipCount": 5, - "clips": [ - { - "clipName": "俯身A", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.0 - }, - { - "clipName": "俯身B", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.25 - }, - { - "clipName": "微笑", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.5 - }, - { - "clipName": "眯眼A", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 0.75 - }, - { - "clipName": "眯眼B", - "position": { - "x": 0.0, - "y": 0.0, - "magnitude": 0.0, - "sqrMagnitude": 0.0 - }, - "threshold": 1.0 - } - ], - "minThreshold": 0.0, - "maxThreshold": 1.0, - "blendParameter": "Face Param", - "blendParameterY": "Is Talking" - } -] \ No newline at end of file + "threshold": 1.0 + } + ], + "minThreshold": 0.0, + "maxThreshold": 1.0, + "blendParameter": "Face Param", + "blendParameterY": "Is Talking" + } + ] +} \ No newline at end of file diff --git a/Assets/Scripts/Framework/OtherKit/AnimatorKit.cs b/Assets/Scripts/Framework/OtherKit/AnimatorKit.cs index 53c9c19cc..908ec95bc 100644 --- a/Assets/Scripts/Framework/OtherKit/AnimatorKit.cs +++ b/Assets/Scripts/Framework/OtherKit/AnimatorKit.cs @@ -18,8 +18,9 @@ namespace AibisDream.Framework /// 解析 AnimatorController 下的所有混合树 /// /// AnimatorController 资产 + /// 是否包含嵌套的混合树(子混合树),默认为 true /// 混合树信息列表 - public static List ParseBlendTree(AnimatorController controller) + public static List ParseBlendTree(AnimatorController controller, bool includeNested = true) { if (controller == null) { @@ -34,7 +35,7 @@ namespace AibisDream.Framework { if (layer.stateMachine != null) { - CollectBlendTreesFromStateMachine(layer.stateMachine, blendTreeInfos); + CollectBlendTreesFromStateMachine(layer.stateMachine, blendTreeInfos, includeNested); } } @@ -44,7 +45,7 @@ namespace AibisDream.Framework /// /// 从状态机中收集混合树(递归处理子状态机) /// - private static void CollectBlendTreesFromStateMachine(AnimatorStateMachine stateMachine, List blendTreeInfos) + private static void CollectBlendTreesFromStateMachine(AnimatorStateMachine stateMachine, List blendTreeInfos, bool includeNested) { if (stateMachine == null) return; @@ -53,7 +54,7 @@ namespace AibisDream.Framework { if (state.state?.motion is BlendTree blendTree) { - CollectBlendTreeInfo(blendTree, blendTreeInfos); + CollectBlendTreeInfo(blendTree, blendTreeInfos, includeNested); } } @@ -62,15 +63,18 @@ namespace AibisDream.Framework { if (subStateMachine.stateMachine != null) { - CollectBlendTreesFromStateMachine(subStateMachine.stateMachine, blendTreeInfos); + CollectBlendTreesFromStateMachine(subStateMachine.stateMachine, blendTreeInfos, includeNested); } } } /// - /// 收集混合树信息(递归处理子混合树) + /// 收集混合树信息(可选递归处理子混合树) /// - private static void CollectBlendTreeInfo(BlendTree blendTree, List blendTreeInfos) + /// 混合树 + /// 混合树信息列表 + /// 是否包含嵌套的混合树(子混合树) + private static void CollectBlendTreeInfo(BlendTree blendTree, List blendTreeInfos, bool includeNested) { if (blendTree == null) return; @@ -103,10 +107,10 @@ namespace AibisDream.Framework }; info.clips.Add(clipInfo); - // 如果子 Motion 也是 BlendTree,递归处理 - if (child.motion is BlendTree childBlendTree) + // 如果子 Motion 也是 BlendTree,根据 includeNested 参数决定是否递归处理 + if (includeNested && child.motion is BlendTree childBlendTree) { - CollectBlendTreeInfo(childBlendTree, blendTreeInfos); + CollectBlendTreeInfo(childBlendTree, blendTreeInfos, includeNested); } } } @@ -132,10 +136,10 @@ namespace AibisDream.Framework }; info.clips.Add(clipInfo); - // 如果子 Motion 也是 BlendTree,递归处理 - if (child.motion is BlendTree childBlendTree) + // 如果子 Motion 也是 BlendTree,根据 includeNested 参数决定是否递归处理 + if (includeNested && child.motion is BlendTree childBlendTree) { - CollectBlendTreeInfo(childBlendTree, blendTreeInfos); + CollectBlendTreeInfo(childBlendTree, blendTreeInfos, includeNested); } } } @@ -152,16 +156,16 @@ namespace AibisDream.Framework #endif /// - /// 从 JSON 字符串解析混合树信息列表 + /// 从 JSON 字符串解析混合树配置(包含默认状态名和混合树列表) /// /// JSON 字符串 - /// 混合树信息列表 - public static List ParseBlendTreeFromJson(string jsonString) + /// 混合树配置 + public static BlendTreeConfig ParseBlendTreeConfigFromJson(string jsonString) { if (string.IsNullOrEmpty(jsonString)) { Debug.LogWarning("JSON 字符串为空"); - return new List(); + return null; } try @@ -172,21 +176,31 @@ namespace AibisDream.Framework ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; - // 反序列化 JSON - var blendTreeInfos = JsonConvert.DeserializeObject>(jsonString, settings); - - if (blendTreeInfos == null) + // 优先按新结构(包含默认状态名)反序列化 + var config = JsonConvert.DeserializeObject(jsonString, settings); + if (config != null && config.blendTrees != null && config.blendTrees.Count > 0) { - Debug.LogWarning("JSON 反序列化结果为 null"); - return new List(); + return config; } - return blendTreeInfos; + // 兼容旧结构:仅为 List + var blendTreeInfos = JsonConvert.DeserializeObject>(jsonString, settings); + if (blendTreeInfos != null) + { + return new BlendTreeConfig + { + defaultStateName = string.Empty, + blendTrees = blendTreeInfos + }; + } + + Debug.LogWarning("JSON 反序列化结果为 null"); + return null; } catch (Exception e) { Debug.LogError($"从 JSON 解析混合树信息失败: {e.Message}"); - return new List(); + return null; } } @@ -196,11 +210,22 @@ namespace AibisDream.Framework /// JSON 文件路径(可以是 Assets 相对路径或绝对路径) /// 混合树信息列表 public static List ParseBlendTreeFromJsonFile(string jsonFilePath) + { + var config = ParseBlendTreeConfigFromJsonFile(jsonFilePath); + return config?.blendTrees ?? new List(); + } + + /// + /// 从 JSON 文件路径解析混合树配置(包含默认状态名和混合树列表) + /// + /// JSON 文件路径(可以是 Assets 相对路径或绝对路径) + /// 混合树配置 + public static BlendTreeConfig ParseBlendTreeConfigFromJsonFile(string jsonFilePath) { if (string.IsNullOrEmpty(jsonFilePath)) { Debug.LogWarning("JSON 文件路径为空"); - return new List(); + return null; } try @@ -224,23 +249,40 @@ namespace AibisDream.Framework if (!File.Exists(fullPath)) { Debug.LogWarning($"JSON 文件不存在: {fullPath}"); - return new List(); + return null; } // 读取文件内容 string jsonContent = File.ReadAllText(fullPath, System.Text.Encoding.UTF8); // 调用字符串解析方法 - return ParseBlendTreeFromJson(jsonContent); + return ParseBlendTreeConfigFromJson(jsonContent); } catch (Exception e) { Debug.LogError($"从 JSON 文件解析混合树信息失败: {e.Message}"); - return new List(); + return null; } } } + /// + /// 混合树配置(包含默认状态名和混合树列表) + /// + [System.Serializable] + public class BlendTreeConfig + { + /// + /// AnimatorController 的默认 State 名称 + /// + public string defaultStateName; + + /// + /// 混合树信息列表 + /// + public List blendTrees; + } + /// /// 混合树信息数据结构 /// diff --git a/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs b/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs index 226d31976..1cb8e54ac 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs @@ -20,7 +20,7 @@ namespace AibisDream var controller = ResourceKit.LoadAssetSync($"Animation/{ActorName}"); _animator.runtimeAnimatorController = controller; - _blendTreeDict = ParseBlendTreeInfos(); + ParseBlendTreeInfos(); // 保存事件 actorData.onSave.AddListener(() => @@ -40,20 +40,21 @@ namespace AibisDream } } - private Dictionary ParseBlendTreeInfos() + private void ParseBlendTreeInfos() { - var dict = new Dictionary(); + _blendTreeDict = new Dictionary(); // 解析混合树 var configText = ResourceKit.LoadAssetSync($"AnimationConfig/{ActorName}"); if (configText != null) { - var blendTreeInfos = AnimatorKit.ParseBlendTreeFromJson(configText.text); - foreach (var info in blendTreeInfos) + var blendTreeConfig = AnimatorKit.ParseBlendTreeConfigFromJson(configText.text); + foreach (var info in blendTreeConfig.blendTrees) { - dict[info.name] = info; + _blendTreeDict[info.name] = info; } + + _currentStateName = blendTreeConfig.defaultStateName; } - return dict; } /// @@ -61,30 +62,34 @@ namespace AibisDream /// /// public override void ChangeState(string stateName) + { + StartCoroutine(ChangeStateAsync(stateName)); + } + + public override IEnumerator ChangeStateAsync(string stateName) { // 记录当前状态 _currentStateName = stateName; // 切换站姿 _animator.SetTrigger(stateName); - } - - public override IEnumerator ChangeStateAsync(string stateName) - { - ChangeState(stateName); // 等待动作切换完成 while (!_animator.GetCurrentAnimatorStateInfo(0).IsName(stateName)) { yield return null; } - var info = _animator.GetCurrentAnimatorStateInfo(0); - yield return new WaitForSeconds(info.length); + // var info = _animator.GetCurrentAnimatorStateInfo(0); + // yield return new WaitForSeconds(info.length); + + _animator.ResetTrigger(stateName); + + Debug.Log($"当前状态{_currentStateName}"); } public override void SetTalkingState(bool isTalking) { - _animator.SetBool("Is Talking", isTalking); + _animator.SetFloat("Is Talking", isTalking ? 1 : 0); } public override void SetFaceState(string faceName) @@ -92,6 +97,7 @@ namespace AibisDream // 先读取当前混合树信息 if (_blendTreeDict.TryGetValue(_currentStateName, out var blendTreeInfo)) { + Debug.Log($"当前状态{_currentStateName}的混合树信息存在"); if (blendTreeInfo.type == "1D") { var clipInfo = blendTreeInfo.GetClipInfo(faceName); diff --git a/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs b/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs index b63f6c509..7a991ed2c 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs @@ -232,7 +232,7 @@ namespace AibisDream } [YarnCommand("set_actor_face")] - public static void SetFaceState(string actorName = ClinicSlotName, string faceName = "默认") + public static void SetFaceState(string faceName, string actorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { diff --git a/Assets/Scripts/Test/ActorAnimaExTest.cs b/Assets/Scripts/Test/ActorAnimaExTest.cs index 2bad68762..93633c6fc 100644 --- a/Assets/Scripts/Test/ActorAnimaExTest.cs +++ b/Assets/Scripts/Test/ActorAnimaExTest.cs @@ -182,8 +182,8 @@ namespace AibisDream.Test return; } - _blendTreeInfos = AnimatorKit.ParseBlendTreeFromJson(configText.text); - + _blendTreeInfos = AnimatorKit.ParseBlendTreeConfigFromJson(configText.text).blendTrees; + if (_blendTreeInfos == null || _blendTreeInfos.Count == 0) { Debug.LogWarning("混合树配置为空或解析失败"); @@ -191,7 +191,8 @@ namespace AibisDream.Test } // 提取状态名称列表 - _stateNames = _blendTreeInfos.Select(info => info.name).ToList(); + // _stateNames = _blendTreeInfos.Select(info => info.name).ToList(); + _stateNames = new List {"站立", "俯身"}; // 为每个状态提取表情名称列表 _faceNamesByState.Clear(); @@ -235,6 +236,9 @@ namespace AibisDream.Test return; } + // 更新索引 + _currentStateIndex = (_currentStateIndex + 1) % _stateNames.Count; + // 循环切换到下一个状态 string nextState = _stateNames[_currentStateIndex]; Debug.Log($"=== 测试 2: 切换状态到 '{nextState}' ==="); @@ -242,9 +246,6 @@ namespace AibisDream.Test _testActor.ChangeState(nextState); Debug.Log($"✓ 状态已切换到: {nextState}"); - - // 更新索引 - _currentStateIndex = (_currentStateIndex + 1) % _stateNames.Count; } ///