Unity Spine AnimationReferenceAsset 使用笔记


动画模块:性能优化方案




using UnityEngine;
using UnityEditor;
using Spine.Unity;
using System.IO;
using System.Collections.Generic;

public class SpineAssetTools
{
    // -----------------------------------------------------------
    // 菜单入口 1:针对单个文件(保留这个功能,偶尔修补用)
    // -----------------------------------------------------------
    [MenuItem( "Assets/Spine/生成动作引用 (选中文件)", false, 10 )]
    public static void CreateFromSingleFile( )
    {
        var selectedObj = Selection.activeObject as SkeletonDataAsset;
        if ( selectedObj == null ) return;

        GenerateForSkeleton( selectedObj );

        AssetDatabase.SaveAssets( );
        AssetDatabase.Refresh( );
        Debug.Log( $"[Spine工具] 单个文件处理完成: {selectedObj.name}" );
    }

    [MenuItem( "Assets/Spine/生成动作引用 (选中文件)", true )]
    public static bool ValidateSingleFile( )
    {
        return Selection.activeObject is SkeletonDataAsset;
    }

    // -----------------------------------------------------------
    // 菜单入口 2:针对文件夹(递归批量处理)
    // -----------------------------------------------------------
    [MenuItem( "Assets/Spine/批量生成动作引用 (选中文件夹)", false, 11 )]
    public static void CreateFromFolder( )
    {
        string selectedPath = AssetDatabase.GetAssetPath( Selection.activeObject );

        // 1. 查找该目录下所有 SkeletonDataAsset
        // "t:SkeletonDataAsset" 是 Unity 搜索语法,效率极高
        string[] guids = AssetDatabase.FindAssets( "t:SkeletonDataAsset", new[] { selectedPath } );

        if ( guids.Length == 0 )
        {
            EditorUtility.DisplayDialog( "提示", "该文件夹下没有找到任何 Spine 骨骼数据!", "知道啦" );
            return;
        }

        // 2. 开启进度条,防止卡死焦虑
        try
        {
            int count = 0;
            for ( int i = 0; i < guids.Length; i++ )
            {
                string assetPath = AssetDatabase.GUIDToAssetPath( guids[ i ] );
                var skeletonDataAsset = AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>( assetPath );

                // 更新进度条
                EditorUtility.DisplayProgressBar( "Spine 批量生成中...",
                    $"正在处理 ({i + 1}/{guids.Length}): {skeletonDataAsset.name}",
                    ( float ) i / guids.Length );

                if ( skeletonDataAsset != null )
                {
                    GenerateForSkeleton( skeletonDataAsset );
                    count++;
                }
            }
            Debug.Log( $"[Spine工具] 批量处理完成!共处理了 {count} 个骨骼文件。" );
        }
        catch ( System.Exception e )
        {
            Debug.LogError( $"[Spine工具] 发生错误: {e.Message}" );
        }
        finally
        {
            // 3. 无论成功失败,都要清除进度条并保存
            EditorUtility.ClearProgressBar( );
            AssetDatabase.SaveAssets( );
            AssetDatabase.Refresh( );
        }
    }

    [MenuItem( "Assets/Spine/批量生成动作引用 (选中文件夹)", true )]
    public static bool ValidateFolder( )
    {
        // 只有选中文件夹时才显示此菜单
        return Selection.activeObject is DefaultAsset;
    }

    // -----------------------------------------------------------
    // 核心修复:使用 SerializedObject 写入 protected 字段
    // -----------------------------------------------------------
    private static void GenerateForSkeleton( SkeletonDataAsset skeletonDataAsset )
    {
        if ( skeletonDataAsset == null ) return;
        var skeletonData = skeletonDataAsset.GetSkeletonData( true );
        if ( skeletonData == null ) return;

        string assetPath = AssetDatabase.GetAssetPath( skeletonDataAsset );
        string directory = Path.GetDirectoryName( assetPath );
        string targetFolder = Path.Combine( directory, skeletonDataAsset.name + "_Anims" );

        if ( !Directory.Exists( targetFolder ) ) Directory.CreateDirectory( targetFolder );

        foreach ( var anim in skeletonData.Animations )
        {
            string animName = anim.Name;
            // 处理非法字符
            string safeAnimName = animName.Replace( "/", "_" ).Replace( "\\", "_" );
            string fileName = $"Ref_{skeletonDataAsset.name}_{safeAnimName}.asset";
            string fullPath = Path.Combine( targetFolder, fileName );

            // 如果文件已存在,跳过
            if ( File.Exists( fullPath ) ) continue;

            // 1. 创建空实例
            AnimationReferenceAsset refAsset = ScriptableObject.CreateInstance<AnimationReferenceAsset>( );

            // 2. 【关键】使用 SerializedObject 包装它,绕过 protected 限制
            SerializedObject so = new SerializedObject( refAsset );

            // 3. 查找 protected 属性 (对应 Spine 源码中的字段名)
            SerializedProperty skeletonProp = so.FindProperty( "skeletonDataAsset" );
            SerializedProperty animNameProp = so.FindProperty( "animationName" );

            // 4. 赋值
            if ( skeletonProp != null ) skeletonProp.objectReferenceValue = skeletonDataAsset;
            if ( animNameProp != null ) animNameProp.stringValue = animName;

            // 5. 应用修改
            so.ApplyModifiedPropertiesWithoutUndo( );

            // 6. 保存到磁盘
            AssetDatabase.CreateAsset( refAsset, fullPath );
        }
    }

}


3.1 问题痛点

传统写法 SetAnimation(0, "Run", true) 存在隐患:

  1. 性能消耗:Spine 内部需遍历 List 并进行字符串 Hash 对比,高频调用(如 Update)会产生显著 CPU 开销。
  2. 维护风险:美术修改动作名会导致代码失效,且由字符串硬编码引起,编译器无法报错。

3.2 解决方案:AnimationReferenceAsset

废弃字符串调用,全面采用 Spine-Unity 提供的 AnimationReferenceAsset 资源引用流程。

代码规范示例:

using Spine.Unity;

public class UnitController : MonoBehaviour
{
    public SkeletonAnimation skeletonAnim;

    [Header("动作资源配置")]
    public AnimationReferenceAsset animIdle;   // 在 Inspector 拖入 Asset
    public AnimationReferenceAsset animRun;

    public void PlayRun()
    {
        // 直接传递 Asset,内部自动解包 Animation 对象,无字符串查找开销
        if (animRun != null)
            skeletonAnim.AnimationState.SetAnimation(0, animRun, true);
    }
}




4. 工具链:自动化工作流

为避免手动创建数以千计的 AnimationReferenceAsset,项目组提供了自动化生成工具。

4.1 工具脚本

脚本路径:Assets/Editor/SpineAssetTools.cs
(脚本代码已在版本库中归档,此处略,核心功能见下文)

4.2 操作指南

  1. 选中目标:在 Project 窗口选中某个怪物的文件夹(如 Assets/Art/Characters/Boss_01)或整个角色总目录
  2. 执行生成:右键点击 -> 选择菜单 Assets/Spine/批量生成动作引用 (选中文件夹)
  3. 结果:工具会自动扫描目录下所有 SkeletonDataAsset,并在其同级目录创建 [SkeletonName]_Anims 文件夹,生成所有动作的 Asset 引用。
  4. 配置:程序在 Inspector 面板直接搜索生成的 Asset 名称进行赋值。

-

Unity 摇杆

评 论