添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

1. 介绍

Unity Spine 是一个收费的跨平台的强大的 2D 骨骼动画工具,它能够轻松创建复杂的角色动画。

要在 unity 中使用 spine 动画的话,需要下载对应的 spine 运行库

⚠️注意使用的 spine 运行库版本要与 unity 版本相对应,

对应关系见下图:

2. 优点

  • 轻量级和高效:Spine 动画使用基于骨骼的动画技术,相比传统的逐帧动画,它们具有更小的文件大小和更低的内存占用。这使得 Spine 动画在移动设备和低端硬件上的性能更好。
  • 灵活性:使用 Spine,你可以创建高度可定制的动画,包括骨骼的变形、缩放、旋转和平移。你可以轻松地调整动画的速度、混合不同的动画,以及实现复杂的角色动画控制。
  • 运行时动画:Spine 动画可以在运行时进行实时修改和控制。这意味着你可以通过代码来改变动画的播放状态,根据角色的行为和环境条件进行相应的动画交互。
  • 平台兼容性:Unity 支持多个平台,包括 PC、移动设备和主机,而 Spine 动画可以轻松地在这些平台上进行部署和播放。
  • 3.spine 导出的 unity 资源

    一般情况下,美术会导出下列 3 个文件

    .json 存储骨骼信息
    .png 使用的图片图集
    .atlas.txt 图片在图集中的位置信息
    当我们把这三个资源导入到已经引入了 Spine 运行库的 Unity 工程后会自动为我们生成

    _Atlas 材质和.atlas.txt 文件的引用配置文件
    _Material 材质文件
    _SkeletonData json 和 _Atlas 资源的引用配置文件

    ⚠️:但使用 .json 格式读取动画数据是比较慢且运行效率较低的方式。

    因为 spine 动画使用 json 文件后期优化效果不太好,容易造成卡顿,加载过慢等。而二进制文件加载速度就比较快了。

    Spine 支持 Binary format ,二进制的数据导出,采用这种方式导出的格式是:.png 、.skel 和 .atlas

    4. 导入

    1. 在图集.atlas 后面加上.txt 后缀

    2. 在二进制文件.skel 后面加上.betys 后缀

    5. 导入报错的解决方案

    1. 可能是美术那边导出时出现的问题。你可以让美术那边再导出一次。同一个 spine 动画让美术导出 json 文件和二进制文件。如果 json 文件导入也出现错误,那就是美术那边导出的问题。

    2. 如果导入 json 没问题。导入二进制有问题,那么可能是运行库版本和 spine 版本不一致的问题。首先你可以查看一下 untiy 中 spine 运行库的版本是多少。是否和 spine 版本一致。

    3. 二进制文件导入 unity 中后如果没有自动实例化对象,那么你需要手动创建。在创建的过程中有时候你会发现你创建的对象在场景中使用的时候变大了。具体说是变大了 100 倍。有人说我创建的时候也修改 scale 的大小了啊(0.01),怎么还会那么大呢?这其中就有些技巧了,你创建的时候先修改 scale 值,修改完之后再将需要的文件拖到相应的位置。那么你创建的大小就是缩放后的正常的大小。在场景中直接使用就是美术给你的正常大小了。

    6. 使用

    **1.unity 中添加 spine 的三种方式 ** ,如下图:

    ** 2.ugui 中使用 SkeletonGraphic(UnityUI)**

    3. 其他使用 SkeletonAnimation

    4. 一般来说,在以下情况下你可能会使用 SkeletonRenderer:

  • 角色动画:如果你的游戏中有 2D 角色需要进行复杂的骨骼动画,你可以使用 Spine 创建角色的动画,并将 SkeletonRenderer 组件添加到 Unity 场景中的角色对象上。然后,该组件将负责在运行时渲染角色的骨骼动画。
  • UI 动画:你可以使用 Spine 创建 2D UI 元素的动画,例如按钮、图标、菜单等。通过将 SkeletonRenderer 添加到 UI 元素上,你可以在运行时播放 Spine 动画来增强用户界面的交互和视觉效果。
  • 敌人 / 怪物动画:如果你的游戏中有敌人、怪物或 NPC 需要复杂的动画效果,你可以使用 Spine 创建骨骼动画,并将 SkeletonRenderer 组件添加到相应的游戏对象上。
  • 特效动画:Spine 还可以用于创建 2D 特效动画,比如爆炸、火焰、魔法等。将 SkeletonRenderer 组件与特效对象一起使用,可以实现更加生动逼真的效果。
  • 7. 代码示例

    1. 加载 Spine 骨骼动画:

    使用 Spine 的 SkeletonDataAsset 类来加载 Spine 骨骼数据,并通过 SkeletonAnimation 组件来播放动画。

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }}
    
  • 首先,您需要在 Unity 中创建一个空 GameObject,并附加 SkeletonAnimation 组件。这个组件用于播放 Spine 动画。
  • 创建一个 public 字段来存储 SkeletonDataAsset,这是 Spine 动画的骨骼数据。
  • 在 Start() 方法中,获取 SkeletonAnimation 组件的引用,并将 SkeletonDataAsset 赋值给 skeletonAnimation.skeletonDataAsset。
  • 调用 Initialize(true) 方法来初始化 SkeletonAnimation。传入 true 表示启用 MeshRenderer,以便在场景中显示骨骼动画。
  • 2. 控制 Spine 动画的播放:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Space))        {            skeletonAnimation.AnimationState.SetAnimation(0, "walk", true);        }    }}
    
  • 在 Update()方法中,我们可以使用 Input.GetKeyDown(KeyCode.Space) 来检测是否按下了空格键。
  • 如果按下了空格键,我们通过 skeletonAnimation.AnimationState.SetAnimation() 方法来播放名为 "walk" 的动画。
  • 第一个参数 0 表示 trackIndex,表示要将动画放置在哪个轨道(track)上。Spine 允许在同一时间在多个轨道上播放多个动画,0 是默认的轨道。
  • 第二个参数是动画名称,这里是 "walk"。您可以将其替换为其他 Spine 动画的名称。
  • 第三个参数 true 表示循环播放动画,如果设置为 false,则动画只会播放一次。
  • 3. 暂停和恢复动画播放:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Space))        {            skeletonAnimation.AnimationState.SetAnimation(0, "walk", true);        }         if (Input.GetKeyDown(KeyCode.P))        {            if (skeletonAnimation.AnimationState.GetCurrent(0) != null)            {                skeletonAnimation.AnimationState.GetCurrent(0).TimeScale = 0f; // Pause animation            }        }         if (Input.GetKeyDown(KeyCode.R))        {            if (skeletonAnimation.AnimationState.GetCurrent(0) != null)            {                skeletonAnimation.AnimationState.GetCurrent(0).TimeScale = 1f; // Resume animation            }        }    }}
    
  • 在 Update() 方法中,我们添加了检测按下 "P" 和 "R" 键的代码来实现暂停和恢复动画的播放。
  • 当按下 "P" 键时,我们通过 GetCurrent(0) 方法获取当前正在播放的动画轨道,并将其 TimeScale 设置为 0,这会将动画暂停。
  • 当按下 "R" 键时,我们将动画轨道的 TimeScale 设置为 1,这会恢复动画的正常播放。
  • 4. 监听动画事件:

    using Spine;using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);         // 订阅动画事件        skeletonAnimation.AnimationState.Event += HandleAnimationEvent;    }     private void HandleAnimationEvent(TrackEntry trackEntry, Spine.Event e)    {        Debug.Log("Animation Event: " + e.Data.Name);        // 在此处执行事件相关的逻辑    }}
    
  • 在 Start() 方法中,我们通过 skeletonAnimation.AnimationState.Event += HandleAnimationEvent; 订阅了动画事件。
  • 创建 HandleAnimationEvent 方法来处理动画事件。
  • 当 Spine 动画的事件被触发时,HandleAnimationEvent 将会被调用,并且您可以在其中执行与事件相关的逻辑。例如,您可以在 Spine 编辑器中为某个动画帧添加事件,并在代码中根据事件的名称来触发相应的逻辑。
  • 5. 切换皮肤(换装)

    using Spine.Unity; public class SpineSkinController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    private string currentSkinName;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);         // 设置初始皮肤        currentSkinName = "default";        skeletonAnimation.Skeleton.SetSkin(currentSkinName);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.S))        {            // 切换到另一个皮肤            string newSkinName = "alternate";            skeletonAnimation.Skeleton.SetSkin(newSkinName);            skeletonAnimation.Skeleton.SetSlotsToSetupPose(); // 刷新插槽以更新换装后的显示            skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);        }    }}
    
  • 在 Start()方法中,我们通过 skeletonAnimation.Skeleton.SetSkin(currentSkinName) 设置了初始的皮肤,这里使用了 "default" 皮肤。
  • 在 Update()方法中,当按下 "S" 键时,我们通过 skeletonAnimation.Skeleton.SetSkin(newSkinName) 来切换到另一个皮肤,这里使用了 "alternate" 皮肤。
  • 切换皮肤后,为了确保新的皮肤在场景中立即显示,我们还需要调用 skeletonAnimation.Skeleton.SetSlotsToSetupPose()刷新插槽,以更新换装后的显示。最后,通过 skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton) 将更改应用到动画状态。
  • 6. 获取骨骼的 Transform 信息:

    using Spine.Unity;using UnityEngine; public class SpineBoneTransform : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.G))        {            // 获取指定骨骼的Transform信息            Bone bone = skeletonAnimation.Skeleton.FindBone("boneName");            if (bone != null)            {                Vector3 bonePosition = new Vector3(bone.WorldX, bone.WorldY, 0f);                Quaternion boneRotation = Quaternion.Euler(0f, 0f, bone.WorldRotationX);                Vector3 boneScale = new Vector3(bone.WorldScaleX, bone.WorldScaleY, 1f);                 Debug.Log("Bone Position: " + bonePosition);                Debug.Log("Bone Rotation: " + boneRotation.eulerAngles);                Debug.Log("Bone Scale: " + boneScale);            }        }    }}
    
  • 在 Update()方法中,我们通过 Input.GetKeyDown(KeyCode.G) 检测是否按下 "G" 键。
  • 当按下 "G" 键时,我们使用 skeletonAnimation.Skeleton.FindBone("boneName") 查找指定名称的骨骼(需要将 "boneName" 替换为实际的骨骼名称)。
  • 如果找到了该骨骼,我们可以使用骨骼的 WorldX、WorldY、WorldRotationX 和 WorldScaleX、WorldScaleY 属性来获取骨骼的位置、旋转和缩放信息。注意,这些属性表示骨骼在世界坐标系中的信息。
  • 7. 控制骨骼动画的混合和交叉淡入:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Alpha1))        {            // 播放动画1,并在1秒内淡入混合到目标动画            skeletonAnimation.AnimationState.SetAnimation(0, "animation1", true);        }         if (Input.GetKeyDown(KeyCode.Alpha2))        {            // 播放动画2,并在0.5秒内交叉淡入混合到目标动画            skeletonAnimation.AnimationState.SetAnimation(1, "animation2", true).MixDuration = 0.5f;        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Alpha1) 和 Input.GetKeyDown(KeyCode.Alpha2) 来检测是否按下数字键 1 和 2。
  • 当按下数字键 1 时,我们通过 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation1" 的动画。由于没有设置 MixDuration,这里使用默认的混合时间(通常是 0.2 秒)。
  • 当按下数字键 2 时,我们通过 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation2" 的动画,并设置了 MixDuration 为 0.5 秒。这将导致动画 2 和当前正在播放的动画在 0.5 秒内交叉淡入混合。
  • 8. 控制动画的速度:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public float animationSpeed = 1.0f;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.UpArrow))        {            // 增加动画速度            animationSpeed += 0.5f;            skeletonAnimation.timeScale = animationSpeed;        }         if (Input.GetKeyDown(KeyCode.DownArrow))        {            // 减少动画速度,但不低于0.1            animationSpeed -= 0.5f;            animationSpeed = Mathf.Max(animationSpeed, 0.1f);            skeletonAnimation.timeScale = animationSpeed;        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.UpArrow) 和 Input.GetKeyDown(KeyCode.DownArrow) 来检测是否按下上箭头键和下箭头键。
  • 当按下上箭头键时,我们增加 animationSpeed 变量的值,并将新的值应用到 skeletonAnimation.timeScale,从而增加动画的播放速度。
  • 当按下下箭头键时,我们减少 animationSpeed 变量的值,并确保其不会低于 0.1,然后将新的值应用到 skeletonAnimation.timeScale,从而减少动画的播放速度。
  • 9. 获取动画状态信息:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        TrackEntry currentAnimation = skeletonAnimation.AnimationState.GetCurrent(0);        if (currentAnimation != null)        {            Debug.Log("Current Animation: " + currentAnimation.Animation.Name);            Debug.Log("Animation Time: " + currentAnimation.Time);            Debug.Log("Animation Is Complete: " + currentAnimation.IsComplete);        }    }}
    
  • 在 Update()方法中,我们通过 skeletonAnimation.AnimationState.GetCurrent(0) 获取当前正在播放的动画状态(TrackEntry)。
  • 然后,我们可以通过 TrackEntry 的属性来获取有关当前动画的信息,例如动画名称、动画播放时间和动画是否已经播放完成。
  • 10. 动态替换骨骼纹理

    using Spine.Unity;using UnityEngine; public class SpineTextureReplacement : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    public Texture2D newTexture;    public string slotName;     private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.T))        {            // 获取插槽的当前Attachment并替换纹理            Slot slot = skeletonAnimation.Skeleton.FindSlot(slotName);            if (slot != null)            {                Attachment currentAttachment = slot.Attachment;                if (currentAttachment is RegionAttachment)                {                    RegionAttachment regionAttachment = (RegionAttachment)currentAttachment;                    regionAttachment.SetRegion(newTexture);                    skeletonAnimation.Update(0f); // 强制更新以显示新纹理                }            }        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.T) 检测是否按下 "T" 键。
  • 当按下 "T" 键时,我们通过 skeletonAnimation.Skeleton.FindSlot(slotName) 找到指定名称的插槽(需要将 "slotName" 替换为实际的插槽名称)。
  • 然后,我们获取该插槽的当前 Attachment,并进行类型检查,以确保该 Attachment 是 RegionAttachment(纹理类型的 Attachment)。
  • 如果是 RegionAttachment,我们将其转换为 RegionAttachment,并使用 SetRegion 方法将其纹理替换为 newTexture。
  • 由于更改了 Attachment,我们需要调用 skeletonAnimation.Update(0f) 来强制更新 SkeletonAnimation,以便在场景中显示新的纹理。
  • 11. 播放 Spine 动画的指定轨道:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public int trackIndex = 0;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Alpha1))        {            // 播放动画1在指定轨道            skeletonAnimation.AnimationState.SetAnimation(trackIndex, "animation1", true);        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Alpha1) 来检测是否按下数字键 1。
  • 当按下数字键 1 时,我们通过 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation1" 的动画,并指定了 trackIndex 作为第一个参数。trackIndex 是轨道的索引,用于在多个轨道上播放不同的动画。默认情况下,使用 0 作为轨道索引。
  • 12. 获取当前动画的时间和持续时间:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public int trackIndex = 0;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        TrackEntry currentAnimation = skeletonAnimation.AnimationState.GetCurrent(trackIndex);        if (currentAnimation != null)        {            float currentTime = currentAnimation.Time;            float animationDuration = currentAnimation.Animation.Duration;             Debug.Log("Current Animation Time: " + currentTime);            Debug.Log("Animation Duration: " + animationDuration);        }    }}
    
  • 在 Update()方法中,我们通过 skeletonAnimation.AnimationState.GetCurrent(trackIndex) 获取当前正在播放的动画状态(TrackEntry)。
  • 然后,我们使用 currentAnimation.Time 来获取当前动画的播放时间,以及 currentAnimation.Animation.Duration 来获取当前动画的总持续时间。
  • 13. 控制动画循环次数:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Alpha1))        {            // 播放动画1并设置循环次数            TrackEntry trackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "animation1", true);            trackEntry.Loop = false; // 关闭循环            trackEntry.LoopCount = 2; // 设置循环次数为2次        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Alpha1) 来检测是否按下数字键 1。
  • 当按下数字键 1 时,我们通过 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation1" 的动画,并获取返回的 TrackEntry。
  • 然后,我们可以通过设置 TrackEntry 的 Loop 属性为 false 来关闭动画的循环播放。
  • 同时,通过设置 LoopCount 属性为 2 来指定动画的循环次数为 2 次。
  • 14. 动态更改动画混合的权重:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Alpha1))        {            // 播放动画1并增加混合权重            TrackEntry trackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "animation1", true);            trackEntry.Alpha = 0.5f; // 设置混合权重为0.5        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Alpha1) 来检测是否按下数字键 1。
  • 当按下数字键 1 时,我们通过 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation1" 的动画,并获取返回的 TrackEntry。
  • 然后,我们可以通过设置 TrackEntry 的 Alpha 属性来调整动画的混合权重。Alpha 的取值范围是 0 到 1,1 表示完全显示动画,0 表示完全隐藏动画。在这个例子中,我们将混合权重设置为 0.5,使得动画以一半的透明度进行混合。
  • 15. 暂停和恢复所有动画轨道:

    using Spine.Unity; public class SpineAnimationController : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Space))        {            // 切换暂停和恢复动画            bool isPaused = skeletonAnimation.timeScale == 0f;            skeletonAnimation.timeScale = isPaused ? 1f : 0f;        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Space) 来检测是否按下空格键。
  • 当按下空格键时,我们检查当前动画的时间缩放(timeScale)是否为 0。如果为 0,则表示当前动画处于暂停状态,我们将时间缩放设置为 1 以恢复动画播放。如果不为 0,则表示当前动画正在播放中,我们将时间缩放设置为 0 以暂停动画。
  • 16. 动态创建并替换插槽的 Attachment:

    using Spine;using Spine.Unity;using UnityEngine; public class SpineAttachmentReplacement : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public string slotName;    public Sprite newSprite;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.R))        {            // 获取插槽并替换Attachment为新的Sprite            Slot slot = skeletonAnimation.Skeleton.FindSlot(slotName);            if (slot != null)            {                Attachment currentAttachment = slot.Attachment;                if (currentAttachment is RegionAttachment)                {                    RegionAttachment regionAttachment = (RegionAttachment)currentAttachment;                    Material material = new Material(Shader.Find("Sprites/Default"));                    Material newMaterial = new Material(material); // 复制原始Material                    newMaterial.mainTexture = newSprite.texture; // 设置新的Sprite纹理                    regionAttachment.GetRegion().RenderObject.SetMeshMaterial(newMaterial);                    skeletonAnimation.Update(0f); // 强制更新以显示新Attachment                }            }        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.R) 来检测是否按下 'R' 键。
  • 当按下 'R' 键时,我们通过 skeletonAnimation.Skeleton.FindSlot(slotName) 找到指定名称的插槽(需要将 'slotName' 替换为实际的插槽名称)。
  • 然后,我们获取插槽当前的 Attachment,并进行类型检查,以确保该 Attachment 是 RegionAttachment(纹理类型的 Attachment)。
  • 如果是 RegionAttachment,我们创建一个新的 Material,并将新的 Sprite 纹理赋值给它。
  • 然后,我们使用 regionAttachment.GetRegion().RenderObject.SetMeshMaterial(newMaterial) 来将新的 Material 应用到 Attachment,并强制调用 skeletonAnimation.Update(0f) 来更新 SkeletonAnimation,以显示新的 Attachment。
  • 17. 使用 Spine 动画事件触发 Unity 事件:

    using Spine.Unity;using UnityEngine;using UnityEngine.Events; public class SpineAnimationEventTrigger : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     public UnityEvent onAnimationEvent;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);         // 订阅动画事件        skeletonAnimation.AnimationState.Event += HandleAnimationEvent;    }     private void HandleAnimationEvent(TrackEntry trackEntry, Spine.Event e)    {        if (e.Data.Name == "eventName") // 将"eventName"替换为实际动画中设置的事件名称        {            onAnimationEvent.Invoke(); // 触发Unity事件        }    }}
    
  • 在 Update() 方法中,我们使用 skeletonAnimation.AnimationState.Event += HandleAnimationEvent; 订阅了动画事件。
  • 在 Start() 方法中,我们创建了一个 Unity 事件(UnityEvent),名为 onAnimationEvent。在 Spine 动画中设置的事件名称(例如 "eventName")需要替换为实际动画中设置的事件名称。
  • 当 Spine 动画的事件被触发时,HandleAnimationEvent 方法将会被调用。在这个方法中,我们检查事件的名称是否匹配设定的名称,如果匹配,则触发 Unity 事件 onAnimationEvent.Invoke()。
  • 18. 控制动画播放速度随机化:

    using Spine.Unity;using UnityEngine; public class SpineRandomAnimationSpeed : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public float minSpeed = 0.8f;    public float maxSpeed = 1.2f;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.Space))        {            // 播放动画,并随机设置播放速度            float randomSpeed = Random.Range(minSpeed, maxSpeed);            skeletonAnimation.timeScale = randomSpeed;            skeletonAnimation.AnimationState.SetAnimation(0, "animation1", true);        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.Space) 来检测是否按下空格键。
  • 当按下空格键时,我们随机生成一个播放速度(randomSpeed)在 minSpeed 和 maxSpeed 之间。
  • 然后,我们将 randomSpeed 设置为 skeletonAnimation.timeScale,以调整动画的播放速度,并使用 skeletonAnimation.AnimationState.SetAnimation() 来播放名为 "animation1" 的动画。
  • 19. 动态切换 Spine Atlas 图集:

    using Spine;using Spine.Unity;using UnityEngine; public class SpineAtlasSwitch : MonoBehaviour{    public TextAsset newAtlasText;    public Material newMaterial;    public SkeletonDataAsset skeletonDataAsset;     private SkeletonData currentSkeletonData;    private Atlas currentAtlas;     void Start()    {        LoadNewAtlas();    }     void Update()    {        if (Input.GetKeyDown(KeyCode.S))        {            LoadNewAtlas();        }    }     private void LoadNewAtlas()    {        if (currentAtlas != null)        {            currentAtlas.Dispose();        }         currentAtlas = new Atlas(newAtlasText, "", newMaterial);        currentSkeletonData = SkeletonData.CreateFromatlas(currentAtlas);         skeletonDataAsset.Clear();        skeletonDataAsset.Reset();        skeletonDataAsset.atlasAssets[0].materials[0] = newMaterial;        skeletonDataAsset.atlasAssets[0].materials = new Material[] { newMaterial };        skeletonDataAsset.skeletonJSON = new TextAsset(currentSkeletonData.Json.ToString());        skeletonDataAsset.GetSkeletonData(true);         skeletonDataAsset.GetSkeletonData(false).AssetAtPath("path/to/asset");         skeletonDataAsset.GetSkeletonData(false).FindSlot("slotName");         skeletonDataAsset.GetSkeletonData(false).FindAnimation("animationName");         skeletonDataAsset.GetSkeletonData(false).FindSkin("skinName");    }}
    
  • 这是一个更复杂的用例,用于动态切换 Spine Atlas 图集。我们通过 Input.GetKeyDown(KeyCode.S) 来检测是否按下 "S" 键,以触发图集的切换。
  • 在 LoadNewAtlas() 方法中,加载新的 Atlas 并创建新的 SkeletonData。然后,更新 SkeletonDataAsset 以使用新的 Atlas 和 SkeletonData。
  • 20. 动态创建骨骼动画:

    using Spine;using Spine.Unity;using UnityEngine; public class SpineDynamicAnimation : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;    public string newAnimationName;    public AnimationReferenceAsset newAnimationReference;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);    }     void Update()    {        if (Input.GetKeyDown(KeyCode.N))        {            // 动态创建并播放新的骨骼动画            Animation newAnimation = new Animation(newAnimationName, newAnimationReference.GetAnimation().Timelines);            skeletonAnimation.Skeleton.Data.AddAnimation(newAnimationName, newAnimation, skeletonAnimation.Skeleton.Data.FindAnimation(skeletonAnimation.AnimationState.GetCurrent(0).Animation.Name).Duration, true);             skeletonAnimation.AnimationState.SetAnimation(1, newAnimationName, false);        }    }}
    
  • 在 Update()方法中,我们使用 Input.GetKeyDown(KeyCode.N) 来检测是否按下 "N" 键。
  • 当按下 "N" 键时,我们动态创建一个新的骨骼动画(newAnimation)并将其添加到 SkeletonData 中。这里使用了 newAnimationName 和 newAnimationReference 作为新动画的名称和参考动画。
  • 然后,我们使用 skeletonAnimation.AnimationState.SetAnimation() 来播放新创建的动画。
  • 21. 骨骼动画的事件监听与处理:

    using Spine;using Spine.Unity;using UnityEngine; public class SpineAnimationEvent : MonoBehaviour{    public SkeletonDataAsset skeletonDataAsset;    private SkeletonAnimation skeletonAnimation;     void Start()    {        skeletonAnimation = GetComponent<SkeletonAnimation>();        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;        skeletonAnimation.Initialize(true);         // 订阅动画事件        skeletonAnimation.AnimationState.Event += HandleAnimationEvent;    }     private void HandleAnimationEvent(TrackEntry trackEntry, Spine.Event e)    {        // 根据动画事件名称做相应处理        if (e.Data.Name == "event_name_1")        {            Debug.Log("Event 1 triggered!");            // 在这里添加处理事件1的逻辑        }        else if (e.Data.Name == "event_name_2")        {            Debug.Log("Event 2 triggered!");            // 在这里添加处理事件2的逻辑        }    }}
    
  • 在 Update() 方法中,我们使用 skeletonAnimation.AnimationState.Event += HandleAnimationEvent; 订阅了动画事件。
  • 在 HandleAnimationEvent 方法中,我们根据动画事件的名称(例如 "event_name_1" 和 "event_name_2")来执行不同的处理逻辑。您可以根据动画中设置的事件名称,添加相应的逻辑来响应这些事件。
  • ⚠️注意:这里的用法可能会因 Spine 版本和 Unity 项目结构而有所变化。请根据您的具体情况进行调整。

    8.unity-spine 运行库下载