目录
1.介绍
2.优点
3.spine导出的unity资源
4.导入
5.导入报错的解决方案
6.使用
7.代码示例
1.加载Spine骨骼动画:
2.控制Spine动画的播放:
3.暂停和恢复动画播放:
4.监听动画事件:
5.切换皮肤(换装)
6.获取骨骼的Transform信息:
7.控制骨骼动画的混合和交叉淡入:
8.控制动画的速度:
9.获取动画状态信息:
10.动态替换骨骼纹理:
11.播放Spine动画的指定轨道:
12.获取当前动画的时间和持续时间:
13.控制动画循环次数:
14.动态更改动画混合的权重:
15.暂停和恢复所有动画轨道:
16.动态创建并替换插槽的Attachment:
17.使用Spine动画事件触发Unity事件:
18.控制动画播放速度随机化:
19.动态切换Spine Atlas图集:
20.动态创建骨骼动画:
21.骨骼动画的事件监听与处理:
8.unity-spine运行库下载
Unity Spine是一个收费的跨平台的强大的2D骨骼动画工具,它能够轻松创建复杂的角色动画。
要在unity中使用spine动画的话,需要下载对应的spine运行库
⚠️注意使用的spine运行库版本要与unity版本相对应,
对应关系见下图:
轻量级和高效:Spine动画使用基于骨骼的动画技术,相比传统的逐帧动画,它们具有更小的文件大小和更低的内存占用。这使得Spine动画在移动设备和低端硬件上的性能更好。
灵活性:使用Spine,你可以创建高度可定制的动画,包括骨骼的变形、缩放、旋转和平移。你可以轻松地调整动画的速度、混合不同的动画,以及实现复杂的角色动画控制。
运行时动画:Spine动画可以在运行时进行实时修改和控制。这意味着你可以通过代码来改变动画的播放状态,根据角色的行为和环境条件进行相应的动画交互。
平台兼容性:Unity支持多个平台,包括PC、移动设备和主机,而Spine动画可以轻松地在这些平台上进行部署和播放。
一般情况下,美术会导出下列3个文件
.json 存储骨骼信息
.png 使用的图片图集
.atlas.txt 图片在图集中的位置信息
当我们把这三个资源导入到已经引入了Spine运行库的Unity工程后会自动为我们生成
_Atlas 材质和.atlas.txt文件的引用配置文件
_Material 材质文件
_SkeletonData json和_Atlas资源的引用配置文件
⚠️:但使用 .json 格式读取动画数据是比较慢且运行效率较低的方式。
因为spine动画使用json文件后期优化效果不太好,容易造成卡顿,加载过慢等。而二进制文件加载速度就比较快了。
Spine 支持Binary format ,二进制的数据导出,采用这种方式导出的格式是:.png 、.skel 和 .atlas
1.在图集.atlas后面加上.txt后缀
2.在二进制文件.skel后面加上.betys后缀
1.可能是美术那边导出时出现的问题。你可以让美术那边再导出一次。同一个spine动画让美术导出json文件和二进制文件。如果json文件导入也出现错误,那就是美术那边导出的问题。
2.如果导入json没问题。导入二进制有问题,那么可能是运行库版本和spine版本不一致的问题。首先你可以查看一下untiy中spine运行库的版本是多少。是否和spine版本一致。
3.二进制文件导入unity中后如果没有自动实例化对象,那么你需要手动创建。在创建的过程中有时候你会发现你创建的对象在场景中使用的时候变大了。具体说是变大了100倍。有人说我创建的时候也修改scale的大小了啊(0.01),怎么还会那么大呢?这其中就有些技巧了,你创建的时候先修改scale值,修改完之后再将需要的文件拖到相应的位置。那么你创建的大小就是缩放后的正常的大小。在场景中直接使用就是美术给你的正常大小了。
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组件与特效对象一起使用,可以实现更加生动逼真的效果。
使用Spine的SkeletonDataAsset类来加载Spine骨骼数据,并通过SkeletonAnimation组件来播放动画。
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
skeletonAnimation.Initialize(true);
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
skeletonAnimation.Initialize(true);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
skeletonAnimation.AnimationState.SetAnimation(0, "walk", true);
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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
}
}
}
}
解释:
using Spine;
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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);
// 在此处执行事件相关的逻辑
}
}
解释:
using Spine.Unity;
public class SpineSkinController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
private string currentSkinName;
void Start()
{
skeletonAnimation = GetComponent();
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);
}
}
}
解释:
using Spine.Unity;
using UnityEngine;
public class SpineBoneTransform : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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);
}
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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;
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
public float animationSpeed = 1.0f;
void Start()
{
skeletonAnimation = GetComponent();
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;
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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);
}
}
}
解释:
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.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); // 强制更新以显示新纹理
}
}
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
public int trackIndex = 0;
void Start()
{
skeletonAnimation = GetComponent();
skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
skeletonAnimation.Initialize(true);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// 播放动画1在指定轨道
skeletonAnimation.AnimationState.SetAnimation(trackIndex, "animation1", true);
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
public int trackIndex = 0;
void Start()
{
skeletonAnimation = GetComponent();
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);
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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次
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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
}
}
}
解释:
using Spine.Unity;
public class SpineAnimationController : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
skeletonAnimation.Initialize(true);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// 切换暂停和恢复动画
bool isPaused = skeletonAnimation.timeScale == 0f;
skeletonAnimation.timeScale = isPaused ? 1f : 0f;
}
}
}
解释:
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.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
}
}
}
}
}
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.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事件
}
}
}
解释:
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.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);
}
}
}
解释:
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");
}
}
解释:
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.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);
}
}
}
解释:
using Spine;
using Spine.Unity;
using UnityEngine;
public class SpineAnimationEvent : MonoBehaviour
{
public SkeletonDataAsset skeletonDataAsset;
private SkeletonAnimation skeletonAnimation;
void Start()
{
skeletonAnimation = GetComponent();
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的逻辑
}
}
}
解释:
⚠️注意:这里的用法可能会因Spine版本和Unity项目结构而有所变化。请根据您的具体情况进行调整。
官网
本地下载