动画工具

This commit is contained in:
2026-01-06 18:51:59 +08:00
parent d31146a164
commit e5a72a33b1
11 changed files with 597 additions and 646 deletions
@@ -433,7 +433,7 @@ namespace AibisDream.SystemEditor
AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
}
// 创建AnimationClip
// 创建或更新 AnimationClip(若已存在则在原资源上就地更新,避免引用丢失)
public static AnimationClip CreateAnimationClip(
ProcessedAnimationClip animationData,
List<FrameData> 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<AnimationClip>(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;
@@ -15,6 +15,8 @@ namespace AibisDream.SystemEditor
private List<BlendTreeInfo> _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<string>();
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);
@@ -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}
@@ -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" }
@@ -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}
@@ -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
@@ -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"
}
]
"threshold": 1.0
}
],
"minThreshold": 0.0,
"maxThreshold": 1.0,
"blendParameter": "Face Param",
"blendParameterY": "Is Talking"
}
]
}
@@ -18,8 +18,9 @@ namespace AibisDream.Framework
/// 解析 AnimatorController 下的所有混合树
/// </summary>
/// <param name="controller">AnimatorController 资产</param>
/// <param name="includeNested">是否包含嵌套的混合树(子混合树),默认为 true</param>
/// <returns>混合树信息列表</returns>
public static List<BlendTreeInfo> ParseBlendTree(AnimatorController controller)
public static List<BlendTreeInfo> 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
/// <summary>
/// 从状态机中收集混合树(递归处理子状态机)
/// </summary>
private static void CollectBlendTreesFromStateMachine(AnimatorStateMachine stateMachine, List<BlendTreeInfo> blendTreeInfos)
private static void CollectBlendTreesFromStateMachine(AnimatorStateMachine stateMachine, List<BlendTreeInfo> 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);
}
}
}
/// <summary>
/// 收集混合树信息(递归处理子混合树)
/// 收集混合树信息(可选递归处理子混合树)
/// </summary>
private static void CollectBlendTreeInfo(BlendTree blendTree, List<BlendTreeInfo> blendTreeInfos)
/// <param name="blendTree">混合树</param>
/// <param name="blendTreeInfos">混合树信息列表</param>
/// <param name="includeNested">是否包含嵌套的混合树(子混合树)</param>
private static void CollectBlendTreeInfo(BlendTree blendTree, List<BlendTreeInfo> 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
/// <summary>
/// 从 JSON 字符串解析混合树信息列表
/// 从 JSON 字符串解析混合树配置(包含默认状态名和混合树列表
/// </summary>
/// <param name="jsonString">JSON 字符串</param>
/// <returns>混合树信息列表</returns>
public static List<BlendTreeInfo> ParseBlendTreeFromJson(string jsonString)
/// <returns>混合树配置</returns>
public static BlendTreeConfig ParseBlendTreeConfigFromJson(string jsonString)
{
if (string.IsNullOrEmpty(jsonString))
{
Debug.LogWarning("JSON 字符串为空");
return new List<BlendTreeInfo>();
return null;
}
try
@@ -172,21 +176,31 @@ namespace AibisDream.Framework
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
// 反序列化 JSON
var blendTreeInfos = JsonConvert.DeserializeObject<List<BlendTreeInfo>>(jsonString, settings);
if (blendTreeInfos == null)
// 优先按新结构(包含默认状态名)反序列化
var config = JsonConvert.DeserializeObject<BlendTreeConfig>(jsonString, settings);
if (config != null && config.blendTrees != null && config.blendTrees.Count > 0)
{
Debug.LogWarning("JSON 反序列化结果为 null");
return new List<BlendTreeInfo>();
return config;
}
return blendTreeInfos;
// 兼容旧结构:仅为 List<BlendTreeInfo>
var blendTreeInfos = JsonConvert.DeserializeObject<List<BlendTreeInfo>>(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<BlendTreeInfo>();
return null;
}
}
@@ -196,11 +210,22 @@ namespace AibisDream.Framework
/// <param name="jsonFilePath">JSON 文件路径(可以是 Assets 相对路径或绝对路径)</param>
/// <returns>混合树信息列表</returns>
public static List<BlendTreeInfo> ParseBlendTreeFromJsonFile(string jsonFilePath)
{
var config = ParseBlendTreeConfigFromJsonFile(jsonFilePath);
return config?.blendTrees ?? new List<BlendTreeInfo>();
}
/// <summary>
/// 从 JSON 文件路径解析混合树配置(包含默认状态名和混合树列表)
/// </summary>
/// <param name="jsonFilePath">JSON 文件路径(可以是 Assets 相对路径或绝对路径)</param>
/// <returns>混合树配置</returns>
public static BlendTreeConfig ParseBlendTreeConfigFromJsonFile(string jsonFilePath)
{
if (string.IsNullOrEmpty(jsonFilePath))
{
Debug.LogWarning("JSON 文件路径为空");
return new List<BlendTreeInfo>();
return null;
}
try
@@ -224,23 +249,40 @@ namespace AibisDream.Framework
if (!File.Exists(fullPath))
{
Debug.LogWarning($"JSON 文件不存在: {fullPath}");
return new List<BlendTreeInfo>();
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<BlendTreeInfo>();
return null;
}
}
}
/// <summary>
/// 混合树配置(包含默认状态名和混合树列表)
/// </summary>
[System.Serializable]
public class BlendTreeConfig
{
/// <summary>
/// AnimatorController 的默认 State 名称
/// </summary>
public string defaultStateName;
/// <summary>
/// 混合树信息列表
/// </summary>
public List<BlendTreeInfo> blendTrees;
}
/// <summary>
/// 混合树信息数据结构
/// </summary>
@@ -20,7 +20,7 @@ namespace AibisDream
var controller = ResourceKit.LoadAssetSync<RuntimeAnimatorController>($"Animation/{ActorName}");
_animator.runtimeAnimatorController = controller;
_blendTreeDict = ParseBlendTreeInfos();
ParseBlendTreeInfos();
// 保存事件
actorData.onSave.AddListener(() =>
@@ -40,20 +40,21 @@ namespace AibisDream
}
}
private Dictionary<string, BlendTreeInfo> ParseBlendTreeInfos()
private void ParseBlendTreeInfos()
{
var dict = new Dictionary<string, BlendTreeInfo>();
_blendTreeDict = new Dictionary<string, BlendTreeInfo>();
// 解析混合树
var configText = ResourceKit.LoadAssetSync<TextAsset>($"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;
}
/// <summary>
@@ -61,30 +62,34 @@ namespace AibisDream
/// </summary>
/// <param name="stateName"></param>
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);
@@ -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))
{
+7 -6
View File
@@ -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<string> {"站立", "俯身"};
// 为每个状态提取表情名称列表
_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;
}
/// <summary>