Licensing

将 Spine 官方运行时整合到自建应用程序中需要持有 Spine 许可证.

spine-unity 运行时文档

主要组件

spine-unity运行时提供了一套显示、驱动、跟随和修改Spine导出的skeletons的组件. 如 资产导入 一节中所述, 这些组件将引用导入的skeleton data和texture atlas资产.

将Skeleton添加到场景中

在Unity项目中快速添加Spine skeleton, 只需:

  1. 导入 资产 一节中所述的skeleton data和texture atlas.
  2. _SkeletonData 资产拖入场景(Scene)视图或层级结构(Hierarchy)面板并选择 SkeletonAnimation. 运行时将在场景中实例化一个带有 SkeletonAnimation 的新GameObject.

请注意: 步骤2的另一个实现方法是手动创建这个GameObject:

  1. 点击菜单 GameObject -> Create Empty 新建一个空的GameObject.
  2. 选中GameObject并在检查器中点击 Add Component, 然后选择 SkeletonAnimation. 运行时会自动为GameObject添加 MeshRendererMeshFilter 组件.
  3. 将所需的 _SkeletonData 资产分配到 SkeletonAnimation 组件的 Skeleton Data Asset 属性上.

请注意: 如果在场景视图中只能看见无附件图的skeleton骨骼(bones), 你可以在皮肤的 Initial Skin 属性中选择 default 以外的皮肤.

你现在可以使用组件的C# API来控制skeleton动画, 对动画触发的事件做出反应等等. 更多细节请参考下文.

SkeletonAnimation的平替 - SkeletonGraphic(UI)和SkeletonMecanim

将skeleton实例化为 SkeletonAnimation 是在Unity中使用Spine skeleton的推荐方法, 因为它在三种组件中拥有最完善的功能.

实例化skeleton的三种方法各为:

  1. SkeletonAnimation - 使用Spine定制的动画和事件系统, 提供最高的可定制性. 渲染使用的是 MeshRenderer, 可以像Unity sprite一样同 SpriteMask 等遮罩进行交互. 在Unity中推荐以这种方式使用Spine skeleton.
  2. SkeletonGraphic (UI) - 搭配Unity Canvas 作为UI元素使用. 可以像内置的Unity UI元素一样渲染并与UI遮罩(如 RectMask2D )交互. 动画和事件行为与 SkeletonAnimation 相同.
  3. SkeletonMecanim - 使用Unity的Mecanim动画和事件系统来启动、mix和过渡动画. 相较于SkeletonAnimation, 它提供的动画mix和过渡选项较少. 当使用 SkeletonMecanim 时, 无法保证动画过渡看起来和Spine Editor中的预览效果一致.

进阶操作 - 运行中实例化

请注意: 推荐通过正常工作流将skeleton添加到场景并将其存储于prefabs中, 而不要从实例池中取出池化对象来复用. 这样做会更容易修改和调整skeleton.

虽然这不是推荐的工作流, 但spine-unity API也可以让运行时用 SkeletonDataAsset 实例化 SkeletonAnimationSkeletonGraphic GameObjects, 甚至直接用导出的三种资产来直接实例化组件. 只有当你无法依赖常规Unity导入流程来自动创建 SkeletonDataAssetSpineAtlasAsset 资产时, 才建议直接通过导出资产来实例化.

C#
// instantiating a SkeletonAnimation GameObject from a SkeletonDataAsset
SkeletonAnimation instance = SkeletonAnimation.NewSkeletonAnimationGameObject(skeletonDataAsset);

// instantiating a SkeletonGraphic GameObject from a SkeletonDataAsset
SkeletonGraphic instance
   = SkeletonGraphic.NewSkeletonGraphicGameObject(skeletonDataAsset, transform, skeletonGraphicMaterial);
C#
// instantiation from exported assets without prior import
// 1. Create the AtlasAsset (needs atlas text asset and textures, and materials/shader);
// 2. Create SkeletonDataAsset (needs json or binary asset file, and an AtlasAsset)
SpineAtlasAsset runtimeAtlasAsset
   = SpineAtlasAsset.CreateRuntimeInstance(atlasTxt, textures, materialPropertySource, true);
SkeletonDataAsset runtimeSkeletonDataAsset
   = SkeletonDataAsset.CreateRuntimeInstance(skeletonJson, runtimeAtlasAsset, true);
// 3. Create SkeletonAnimation (needs a valid SkeletonDataAsset)
SkeletonAnimation instance = SkeletonAnimation.NewSkeletonAnimationGameObject(runtimeSkeletonDataAsset);

查看示例场景 Spine Examples/Other Examples/Instantiate from Script 以及示例脚本 SpawnFromSkeletonDataExample.csRuntimeLoadFromExportsExample.csSpawnSkeletonGraphicExample.cs 以详细了解运行时实例化.

SkeletonAnimation组件

SkeletonAnimation 组件是在Unity中使用Spine skeleton的三种方式之一. 这三种方式分别是: 使用 SkeletonAnimation, SkeletonMecanim 或者 SkeletonGraphic (UI) 组件.

SkeletonAnimation 组件是spine-unity运行时的核心. 它可以将Spine skeleton附加到GameObject上, 执行播放动画、响应动画事件等操作.

设置Skeleton Data

SkeletonAnimation 组件需引用skeleton data资产, 它能够给组件提供skeleton的骨骼层次结构、槽位等信息.

如果你通过 拖放 将skeleton添加到场景中, 会自动将skeleton data资产分配到组件中. 如果Skeleton GameObject已设置好但突然需要将skeleto改为不同的资产, 也可以手动更改检查器的属性.

要设置或改变skeleton data, 需要

  1. 选中 SkeletonAnimation 的GameObject
  2. _SkeletonData 资产分配到检查器中的 SkeletonData Asset 属性中.

设置初始皮肤和动画

SkeletonAnimation 检查器公开暴露了以下参数:

  1. Initial Skin. 在动画开始时将显示该皮肤. 请注意: 如果Skeleton的骨骼上没有任何图片, 可以切换到除 default 外的皮肤来使显示正常.
  2. Animation Name. 指定在动画开始时播放的动画.
  3. Loop. 定义初始动画是循环播放还是单次播放.
  4. Time Scale. 设置时间倍率来减慢或加快动画的播放速度.
  5. Unscaled Time. 当置为true时, 更新将基于 Time.unscaledDeltaTime 时间而非 Time.deltaTime. 这对独立于慢动作效果的动画UI元素来说非常有用.

启用Root Motion

SkeletonAnimationSkeletonGraphic (UI) 组件的root motion功能需要搭配独立的 SkeletonRootMotion 组件才能实现. SkeletonAnimation 检查器中的 Root Motion Add Component 按钮可以一键将所需组件添加到skeleton的GameObject上.

进阶设置参数

展开 SkeletonAnimation 检查器中的 Advanced 面板将展示进阶配置参数.

SkeletonAnimation 检查器界面上公开暴露了以下进阶参数

  • Initial Flip X, Initial Flip Y. 在动画开始时, 这两个选项分别将水平翻转和垂直翻转skeleton. 翻转后的skeleton中 ScaleXScaleY 值将被置为 -1.
  • Animation Update. 该选项将决定置是以普通的 Update (默认) 还是用基于物理的 FixedUpdate 方式来更新动画, 或是通过用户调用来手动更新动画. 当使用分配了 RigidbodyRigidbody2DSkeletonRootMotion 组件时, 推荐将更新模式置为In FixedUpdate, 其他情况则建议置为 In Update.
  • Update When Invisible. 设置当MeshRenderer不可见时的更新模式. 当网格恢复可见性时, 更新模式会自动重置为 UpdateMode.FullUpdate.
  • Use single submesh. 如果你的skeleton只使用了单个Material且仅包含单个子网格, 则启用该选项可以简化子网格的生成. 该选项将禁用skeleton上的多种materials渲染、分割渲染和自定义槽位materials功能.
  • Fix Draw Order. 该选项仅适用于使用3个以上子网格 (即渲染顺序交错的多个materials, 例如渲染顺序为 "A B A") 的用例. 若启用该选项, 运行时将在每个material中加入MaterialPropertyBlocks, 以防止LWRP等渲染器对子网格进行过度合批, 过度合批会导致绘制顺序错误 (例如, "A1 B A2" 将变为 "A1 A2 B"). 当绘制正确时, 可以禁用该参数来避免额外的性能开销.
  • Immutable triangles. 附件可见性固定不变时, 则启用该参数可优化skeletons的渲染. 若启用该参数, 那么运行时将不会更新三角形. 如果skeleton不交换或隐藏附件, 或者不使用绘制顺序键(keys), 则应启用此选项来优化渲染. 若不是这样的使用场景, 那么将该参数置为禁用则可能会导致渲染错误.
  • Clear State on Disable. 当禁用该组件或其GameObject时, 将清空渲染状态和skeleton状态. 这可以避免在再次启用它们时还保持着之前的状态. 当把skeleton放进了对象池之后, 启用该设置可能会有所裨益.
  • Fix Prefab Override MeshFilter. 该选项能修复了Prefab总是显示已更改的问题 (将 MeshFilter's 的隐藏标志置为 DontSaveInEditor), 但会导致其他组件丢失对 MeshFilter 的引用. 当置为 Use Global Settings 时, 运行时将使用 Spine首选项 中的设置.
  • Separator Slot Names. 设置渲染分割的槽位. SkeletonRenderSeparator 等组件将读取该设置, 以此在多个GameObjects上用各自独立的渲染器渲染skeleto.
  • Z-Spacing. Skeleton渲染器组件在X/Y平面上自底向顶渲染附件图片. 每个附件都可以在Z轴上自定义z-spacing(z轴间距)值, 以避免 z-fighting 现象.

  • PMA Vertex Colors. 将顶点色的RGB值与顶点色的alpha值相乘. 如果用于渲染的着色器是Spine着色器 (即使用的是 Straight Alpha Texture) 或者是使用了PMA additive blend模式 Blend One OneMinusSrcAlpha 的第三方着色器, 则应启用该参数. 对于使用普通blend模式 Blend SrcAlpha OneMinusSrcAlpha 的普通着色器, 则应禁用此选项. 该参数启用时, 将在同一个绘制调用(draw call)中一起渲染additive槽位和普通槽位. 而禁用该参数时, 需要在additive槽位上启用 SkeletonDataBlend Mode Materials - Apply Additive Material 选项, 不过这样做会产生额外绘制调用, 从而降低性能表现.
  • Tint Black (!). 将black tint顶点数据添加到网格里. 若skeleton中有任一插槽设置了black tint, 则应启用该选项. Black tinting要求着色器将UV2和UV3作为black tint色来实现着色效果. 使用运行时附带的 Spine/Skeleton Tint Black 着色器便能实现. 但若只需tint整个skeleton而非某个特定部件, 则建议你使用 Spine/Skeleton Tint 着色器, 它的效率更高, 且可以通过 MaterialPropertyBlock 改变或动画化 _Black material属性. 更多信息请参见 着色器 一节. 如果在tint多个不同skeletons时希望能进行合批渲染, 建议 用Skeleton.R .G .B .A来tinting各个skeleton.
  • Add Normals. 启用该选项后网格生成器会在输出的网格中添加法线. 如果你的着色器需要顶点法线请启用该选项. 为了更佳性能和更低的内存占用, 建议使用可以接收法线的着色器, 比如 Spine/Skeleton Lit 着色器. 请注意也能将 Spine/Sprite 着色器配置为接收 Fixed Normal.
  • Solve Tangents. 某些光照着色器需要顶点切线(tangents), 通常是应用法线贴图时会用到. 启用该选项后, 运行时每一帧都会计算切线并将其添加到输出网格.
  • Physics Inheritance. 该项控制Transform的运动将如何应用于skeleton的PhysicsConstraints.
    • Position. 该选项中的X和Y缩放值将用于加权Transform的位移量. 当该值非零时, 将把加权Transform位移量应用于skeleton的物理约束. 几种 (X,Y) 的典型值是:
      (1,1) 表示正常应用 XY 移动,
      (2,2) 说明应用了双倍速移动,
      (1,0) 表示仅应用水平移动, 而
      (0,0) 表示完全不影响Transform位移.
    • Rotation. 当该值非零时, 将把该值用于加权Transform的旋转量, 加权旋转量将应用于skeleton的物理约束. 其几种典型值为:
      1 表示正常旋转,
      2 表示二倍速旋转, 而
      0 代表完全不影响Transform旋转.
    • Movement relative to. 设置Transform移动的参考Transform(如父级Transform等), 该选项表示物理约束计算相对移动时应参考的Transform. 将此项置为 None 即表示使用绝对世界位置 (默认值).
  • Add Skeleton Utility. 该按钮可以将用于跟随或覆盖骨骼位置的 SkeletonUtility 组件添加到GameObject上. 更多信息请参见 SkeletonUtility 一节.
  • Debug. 在游戏运行时如果希望了解槽位当前的颜色或骨骼的缩放比例, 按下 Debug 按钮可以打开为此而建的 Skeleton Debug 窗口. 你可以在该窗口中查看skeletion当前的骨骼状态、槽位、约束、绘制顺序、事件和skeletion的统计信息.

SkeletonAnimation的生命周期


SkeletonAnimation 组件中的AnimationState保存了所有正在播放的和队列中的动画的状态. 每次 Update 都会更新AnimationState, 使动画随着时间的推移而推进. 然后将新一帧应用于Skeleton以产生一个新的pose.

C#

通过代码访问 SkeletonAnimation 组件便能与Skeleton交互. 与大多数Unity组件一样, 建议也在查询到引用对象后将其存储起来, 以供后续使用.

C#
...
using Spine.Unity;

public class YourComponent : MonoBehaviour {

   SkeletonAnimation skeletonAnimation;
   Spine.AnimationState animationState;
   Spine.Skeleton skeleton;

   void Awake () {
      skeletonAnimation = GetComponent<SkeletonAnimation>();
      skeleton = skeletonAnimation.Skeleton;
      //skeletonAnimation.Initialize(false); // when not accessing skeletonAnimation.Skeleton,
                                 // use Initialize(false) to ensure everything is loaded.
      animationState = skeletonAnimation.AnimationState;
   }

在SkeletonAnimation的 Update 时间点前后均能执行你的代码. 如果在SkeletonAnimation的 Update 之前用代码获取了Skeleton或骨骼的值, 那么代码中取到的是上一帧而非当前帧的值.

组件会将事件回调委托暴露为属性, 使用这些属性便能通过代码介入 SkeletonAnimation 组件的生命周期, 在运行时计算所有骨骼的世界transforms的之前和之后均可介入. 你可以绑定这些委托来修改骨骼位置和其他Skeleton参数, 而无需关注角色和组件的更新顺序.

SkeletonAnimation Update回调

  • SkeletonAnimation.BeforeApply 在应用该帧动画之前触发该事件. 当你想在动画应用到skeleton上之前改变skeleton状态时, 可以使用该回调.
  • SkeletonAnimation.UpdateLocal 在该帧动画更新完成并应用于skeleton的局部值之后触发该事件. 如果你需要读取或修改骨骼的局部值, 请使用该回调.
  • SkeletonAnimation.UpdateComplete 在Skeleton中所有骨骼的世界值计算完成后触发该事件. 在该事件之后, Update阶段的SkeletonAnimation不再有其他操作. 如果你只需要读取骨骼的世界值, 请使用该回调. 如果其他代码在SkeletonAnimation的Update之后修改了这些值, 这时读取到的世界值仍可能会变化.
  • SkeletonAnimation.UpdateWorld 在计算了Skeleton中所有骨骼的世界值后触发该事件. 若在代码中订阅了该事件, 运行时则将再次调用 skeleton.UpdateWorldTransform. 如果你的skeleton复杂或正在执行其他计算, 该行为将显得非必要甚至有些浪费. 如果需要根据骨骼的世界值来修改骨骼的局部值, 请使用该回调. 该回调在Unity代码中实现自定义约束时会很有帮助.

C#
// your delegate method
void AfterUpdateComplete (ISkeletonAnimation anim) {
   // this is called after animation updates have been completed.
}

// register your delegate method
void Start() {
   skeletonAnimation.UpdateComplete -= AfterUpdateComplete;
   skeletonAnimation.UpdateComplete += AfterUpdateComplete;
}

SkeletonRenderer Update回调

  • OnRebuild 在skeleton成功初始化后触发.
  • OnMeshAndMaterialsUpdated 会在更新完网格和所有material后, 于 LateUpdate() 结束时触发.

C#
// your delegate method for OnMeshAndMaterialsUpdated
void AfterMeshAndMaterialsUpdated (SkeletonRenderer renderer) {
   // this is called after mesh and materials have been updated.
}

// your delegate method for OnRebuild
void AfterRebuild (SkeletonRenderer renderer) {
   // this is called after the Skeleton is successfully initialized.
}

// register your delegate method
void Start() {
   skeletonAnimation.OnMeshAndMaterialsUpdated -= AfterMeshAndMaterialsUpdated;
   skeletonAnimation.OnMeshAndMaterialsUpdated += AfterMeshAndMaterialsUpdated;

   skeletonAnimation.OnRebuild -= AfterRebuild;
   skeletonAnimation.OnRebuild += AfterRebuild;
}

另一种做法是更改代码执行顺序, 使代码在SkeletonAnimation的Update方法后运行.

代码执行顺序

Unity会根据脚本执行顺序 (详见 ExecutionOrderDefaultExecutionOrder) 排序各个组件的 UpdateLateUpdate 调用. 当调用某个组件的 UpdateLateUpdate 方法来修改skeleton或动画状态时, 应重点关注如何在正确的SkeletonAnimation组件更新阶段执行这些代码.

SkeletonAnimation的更新顺序如下:

  • SkeletonAnimation.Update: 更新动画, 将动画应用于skeleton.
  • SkeletonAnimation.LateUpdate: 根据skeleton状态更新skeleton的网格.

若要修改skeleton状态:

  • 若要在 应用动画 之前执行代码, 请在 Update 中调用代码, 并将执行顺序设置 在 SkeletonAnimation 之前.
  • 若要在 应用动画 后到生成skeleton网格前这段时间内执行代码, 同样请在 Update 中调用代码, 但应将执行顺序设置 在 SkeletonAnimation 之后. 也可以在 LateUpdate 中调用, 并将执行顺序设置 在 SkeletonAnimation 之前.

C#
// At execution order -1, this component executes before SkeletonAnimation and SkeletonRenderer.
[DefaultExecutionOrder(-1)]
public class SetupPoseComponent : MonoBehaviour {
   ...

   void Update() {
      // This call lets the skeleton start from setup-pose each frame before applying animations.
      // SetupPoseComponent.Update needs to be called before SkeletonAnimation.Update, which is ensured
      // by [DefaultExecutionOrder(-1)] above.
      skeleton.SetToSetupPose();
   }
}

如果实际情况不允许你将代码执行顺序调得太靠后, 你可以按下一节 手动更新 中所述的做法, 在同一帧中手动更新skeleton或skeleton网格.

手动更新

在进行某些修改后, 可能需要立即将动画重新应用到skeleton上, 或者基于修改后的skeleton重新生成skeleton网格. 相较于 通用运行时, SkeletonAnimation 组件的方法可以实现单次调用便完成一致更新. 下例中, skeleton.UpdateWorldTransform() 就是在 Update(deltaTime)ApplyAnimation() 中调用的.

  • Update(deltaTime) 更新了整个skeleton. Skeleton网格维持不变.
    SkeletonAnimation.Update(deltaTime) 则更新了底层的 AnimationState, 然后在这一帧根据skeleton物理约束推进Transform运动, 并将动画应用于skeleton, 最后更新所有骨骼的世界变换(transform). 当你需要在不推进时间的情况下进行更新整个skeleton, 或者你想按照自定义的delta time来推进更新时, 是有必要的手动维护更新步调的.

C#
// After setting bones and slots to setup pose, perform a full skeleton update without advancing time.
skeleton.SetToSetupPose();
skeletonAnimation.Update(0);

// Setting only slots to setup pose usually (except for when active skin bones change)
// does not require bone world transform updates, so AnimationState.Apply(skeleton) is sufficient.
skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeleton)
C#
// If you don't want to advance by Unity Time.deltaTime or unscaledDeltaTime but by a custom delta time.
skeletonAnimation.timeScale = 0f;

...
skeletonAnimation.Update(customDeltaTime);
  • ApplyAnimation() 将动画重新应用到了skeleton上.
    SkeletonAnimation.ApplyAnimation() 也会将动画重新应用到skeleton上, 但它不会更新底层的 AnimationState 或将Transform的移动传递给物理约束.

C#
// If your script modifies skeleton properties in LateUpdate() and script execution order
// of the script executes after SkeletonAnimation.

   SkeletonAnimation skeletonAnimation;
   Spine.AnimationState animationState;
   Spine.Skeleton skeleton;

void LateUpdate () {
   skeleton.SetToSetupPose(); // perform some skeleton modifications
   skeletonAnimation.Update(0);
   skeletonAnimation.LateUpdateMesh(); // otherwise SkeletonAnimation.LateUpdate would be called next frame
   }

Skeleton

SkeletonAnimation 组件提供了 SkeletonAnimation.Skeleton 属性以便访问底层的 Skeleton. Skeleton存储了对skeleton data资产的引用, 而skeleton data又引用了数个atlas资产.

Skeleton属性可以设置皮肤、缩放比例和附件, 重置骨骼为setup pose以及翻转整个skeleton.

设置附件

设置附件需要传入槽位和附件的名称.

C#
bool success = skeletonAnimation.Skeleton.SetAttachment("slotName", "attachmentName");
C#
// using properties
[SpineSlot] public string slotProperty = "slotName";
[SpineAttachment] public string attachmentProperty = "attachmentName";
...
bool success = skeletonAnimation.Skeleton.SetAttachment(slotProperty, attachmentProperty);

这段代码中的 [SpineSlot][SpineAttachment] 便是 该节 中描述的 String Property Attributes(字符串属性特性).

重置为Setup Pose

程序性动画 有时需要将骨骼和/或槽位重置为其setup pose. 只需如下文 设置皮肤 一节中所述, 在设置好 皮肤 后调用 Skeleton.SetSlotsToSetupPose 即可重置.

C#
skeleton.SetToSetupPose();
skeleton.SetBonesToSetupPose();
skeleton.SetSlotsToSetupPose();

设置皮肤

Spine skeleton可包含多套 皮肤, 皮肤定义了哪张附件图片应该用在哪个槽位上. Skeleton组件提供了一个切换皮肤的简单方法.

C#
bool success = skeletonAnimation.Skeleton.SetSkin("skinName");
skeletonAnimation.Skeleton.SetSlotsToSetupPose(); // see note below
C#
// using properties
[SpineSkin] public string skinProperty = "skinName";
...
bool success = skeletonAnimation.Skeleton.SetSkin(skinProperty);
skeletonAnimation.Skeleton.SetSlotsToSetupPose(); // see note below

若不希望之前设置的附件影响当前附件的可见性, 则应在更改皮肤后调用 Skeleton.SetSlotsToSetupPose. 详情请参见 这篇 文档.

组合皮肤

可以组合Spine皮肤, 比如用一套衣物皮肤组合出一个完整的角色. 更多详情请见 新的皮肤API 文档.

C#
var skeleton = skeletonAnimation.Skeleton;
var skeletonData = skeleton.Data;
var mixAndMatchSkin = new Skin("custom-girl");
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("skin-base"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("nose/short"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("eyelids/girly"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("eyes/violet"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("hair/brown"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("clothes/hoodie-orange"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("legs/pants-jeans"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("accessories/bag"));
mixAndMatchSkin.AddSkin(skeletonData.FindSkin("accessories/hat-red-yellow"));
skeleton.SetSkin(mixAndMatchSkin);
skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // skeletonMecanim.Update() for SkeletonMecanim

运行时重打包

当组合皮肤时会不可避免地会使用到到多种material, 这将导致额外绘制调用. 此时可使用 Skin.GetRepackedSkin() 方法, 将不同皮肤中使用到的texture区域合并为单页texture.

C#
using Spine.Unity.AttachmentTools;

// Create a repacked skin.
Skin repackedSkin = collectedSkin.GetRepackedSkin("Repacked skin", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial, out runtimeMaterial, out runtimeAtlas);
collectedSkin.Clear();

// Use the repacked skin.
skeletonAnimation.Skeleton.Skin = repackedSkin;
skeletonAnimation.Skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // skeletonMecanim.Update() for SkeletonMecanim

// You can optionally clear the cache after multiple repack operations.
AtlasUtilities.ClearCache();

重要须知: 如果重打包失败或打包有误, 其原因多为以下几种:

  1. 禁用了texuture读/写. 你需要在需打包的源textures上启用 Read/Write Enabled 参数.
  2. 启用了压缩: 需要确保源texture的Texture导入设置中 Compression 参数置为 None , 而非 Normal Quality.
  3. 质量等级有误: 使用了half/quarter分辨率的textures. 这个是Unity的bug, 当你使用half/quarter分辨率的texture时, Unity会复制错误的附件区域. 因此请确保Unity项目设置(Project Settings)中的质量等级均置为全分辨率texture.
  4. 源texture尺寸并非2的幂次, 但Unity将其尺寸放大到了最接近的幂次:a) 从Spine导出时启用 Pack Settings 中的 Power of two 选项, 或者b) 确保Unity的Atlas Texture导入设置中的 Non-Power of Two 选项被置为 None.

更多信息请查看示例场景 Spine Examples/Other Examples/Mix and MatchSpine Examples/Other Examples/Mix and Match Equip, 以及 MixAndMatch.cs 示例脚本.

进阶操作-带有法线的运行时重打包

也可以在运行时中把主texture的法线贴图(normal maps)和其他的texture层同时重打包. 只需将 int[] additionalTexturePropertyIDsToCopy = new int[] { Shader.PropertyToID("_BumpMap") }; 作为参数传入 GetRepackedSkin(), 就能将主texture和法线贴图重打包到一起.

C#
Material runtimeMaterial;
Texture2D runtimeAtlas;
Texture2D[] additionalOutputTextures = null;
int[] additionalTexturePropertyIDsToCopy = new int[] { Shader.PropertyToID("_BumpMap") };
Skin repackedSkin = prevSkin.GetRepackedSkin("Repacked skin", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial, out runtimeMaterial, out runtimeAtlas,
additionalTexturePropertyIDsToCopy : additionalTexturePropertyIDsToCopy, additionalOutputTextures : additionalOutputTextures);

// Use the repacked skin.
skeletonAnimation.Skeleton.Skin = repackedSkin;
skeletonAnimation.Skeleton.SetSlotsToSetupPose();
skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); // skeletonMecanim.Update() for SkeletonMecanim

请注意: 法线贴图属性名后缀一般为 "_BumpMap". 当使用自定义着色器时, 一定要保持属性名的一致性. 这个名字是着色器中的属性名, 而非是Unity检查器的 "Normal Map" 标签中显示的字符串.

缩放和翻转Skeleton

垂直或水平翻转skeleton是复用动画的常见方式, 例如一个面向左侧的行走动画以右朝向回放就能作为一段新动画了.

C#
bool isFlippedX = skeleton.ScaleX < 0;
skeleton.ScaleX = -1;
bool isFlippedY = skeleton.ScaleY < 0;
skeleton.ScaleY = -1;

skeleton.ScaleX = -skeleton.ScaleX; // toggle flip x state

手动访问骨骼变换(Transforms)

请注意: 该做法仅适用于极特殊的场合. 使用Spine的 BoneFollowerSkeletonUtilityBone 组件与骨骼交互更简单安全.

Skeleton可以设置和获取骨骼的transform值, 以便实现IK地形适配或者让其他角色和组件(如粒子系统)跟随骨骼运动.

请注意: 由于手动获取和应用新的骨骼位置需要在更新世界transform这一阶段实现, 因此务必要订阅 SkeletonAnimation.UpdateWorld 委托来介入组件生命周期. 否则要么读取到的位置会晚一帧, 要么修改骨骼位置后就被动画覆盖掉.

C#
Bone bone = skeletonAnimation.Skeleton.FindBone("boneName");
Vector3 worldPosition = bone.GetWorldPosition(skeletonAnimation.transform);
// note: when using SkeletonGraphic, all values need to be scaled by the parent Canvas.referencePixelsPerUnit.

Vector3 position = ...;
bone.SetPositionSkeletonSpace(position);

Quaternion worldRotationQuaternion = bone.GetQuaternion();

动画-AnimationState

AnimationState的生命周期

SkeletonAnimation 组件实现了 Update 方法, 它按照delta时间更新底层的 AnimationState, 同时根据skeleton物理约束推进transform运动, 再将 AnimationState 应用于skeleton, 最后更新skeleton上所有骨骼的世界transforms.

Skeleton动画组件通过 SkeletonAnimation.AnimationState 属性暴露了 AnimationState API. 本节假定你熟悉轨道、轨道条目、mix时间或动画队列等概念, 这些内容的详情可见Spine通用运行时指南中的 应用动画 一节.

时间尺度

你可以设置skeleton动画组件的时间尺度(Time Scale)来减慢或加快动画播放. Delta时间用于推进动画, 它直接乘上时间尺度便是动画的播放速度, 例如时间尺度为0.5时, 动画以半速播放, 时间尺度为2时, 动画则会加速为二倍速播放.

C#
float timeScale = skeletonAnimation.timeScale;
skeletonAnimation.timeScale = 0.5f;

设置动画

要设置一个动画, 只需提供其轨道索引(index)、动画名称和并确定是否循环动画.

C#
TrackEntry entry = skeletonAnimation.AnimationState.SetAnimation(trackIndex, "walk", true);
C#
// using properties
[SpineAnimation] public string animationProperty = "walk";
...
TrackEntry entry = skeletonAnimation.AnimationState.SetAnimation(trackIndex, animationProperty, true);

传入 AnimationReferenceAsset 作为参数来替代动画名称也是可以的.

C#
// using AnimationReferenceAsset
public AnimationReferenceAsset animationReferenceAsset; // assign a generated AnimationReferenceAsset to this property
...
TrackEntry entry = skeletonAnimation.AnimationState.SetAnimation(trackIndex, animationReferenceAsset, true);

队列动画

要将动画加入队列, 需要提供轨道索引、动画名称、是否循环播放该动画, 以及该动画在轨道上开始播放的延迟时间(以秒为单位).

C#
TrackEntry entry = skeletonAnimation.AnimationState.AddAnimation(trackIndex, "run", true, 2);
C#
// using properties
[SpineAnimation] public string animationProperty = "run";
...
TrackEntry entry = skeletonAnimation.AnimationState.AddAnimation(trackIndex, animationProperty, true, 2);

空动画与清空动画

Skeleton动画组件提供了设置空动画、队列空动画、清空一条或所有轨道的方法. 这些方法的用法与上文中队列动画的方法和参数相似.

C#
TrackEntry entry = skeletonAnimation.AnimationState.SetEmptyAnimation(trackIndex, mixDuration);
entry = skeletonAnimation.AnimationState.AddEmptyAnimation(trackIndex, mixDuration, delay);
skeletonAnimation.AnimationState.ClearTrack(trackIndex);
skeletonAnimation.AnimationState.ClearTracks();

轨道条目

AnimationState的所有方法都会返回一个 TrackEntry (轨道条目)对象, 它可以进一步定制如何回放某段动画, 也能自定义轨道条目中某个事件的委托. 详见下文中的 处理AnimationState事件 一节.

请注意: 运行时从底层的AnimationState中删除了轨道条目所对应的动画后, 这些方法返回的轨道条目便会失效. Unity的垃圾收集器会自动释放它们. 在接收到轨道条目的销毁事件(dispose event)后, 就不应再存储或访问这个轨道条目了.

C#
TrackEntry entry = ...
entry.EventThreshold = 2;
float trackEnd = entry.TrackEnd;

处理AnimationState事件

底层 AnimationState 在回放动画时将触发以下事件来通知监听器:

  1. 动画播放已开始(started事件) .
  2. 动画播放中断(interrupted事件), 例如清空了一条轨道或设置了一段新动画.
  3. 无中断地完成了动画播放(completed事件) , 如果是循环动画, 该事件可能会多次出现.
  4. 动画播放停止(ended事件) .
  5. 已销毁动画及其的 TrackEntry (disposed事件) .
  6. 用户定义事件触发(event事件).

请注意: 当设置了新动画时中断了之前的动画, 这时触发的不是播放完成( complete )事件, 而是播放中断( interrupt )和播放停止( end )事件.

Skeleton动画组件提供了可绑定的委托, 使得C#代码能够响应任意轨道上任意动画的任意事件. 监听器也可以只绑定某个 TrackEntry 的委托. 所以你既可以监听 AnimationState.Complete 这样的事件来响应所有后续动画的 Complete 事件的, 也可以只监听 TrackEntry.Complete 事件来响应某段具体动画触发的 Complete 事件.

C#

在响应 AnimationState 事件的类中添加监听事件的委托:

C#
SkeletonAnimation skeletonAnimation;
Spine.AnimationState animationState;

void Awake () {
   skeletonAnimation = GetComponent<SkeletonAnimation>();
   animationState = skeletonAnimation.AnimationState;

   // registering for events raised by any animation
   animationState.Start += OnSpineAnimationStart;
   animationState.Interrupt += OnSpineAnimationInterrupt;
   animationState.End += OnSpineAnimationEnd;
   animationState.Dispose += OnSpineAnimationDispose;
   animationState.Complete += OnSpineAnimationComplete;

   animationState.Event += OnUserDefinedEvent;

   // registering for events raised by a single animation track entry
   Spine.TrackEntry trackEntry = animationState.SetAnimation(trackIndex, "walk", true);
   trackEntry.Start += OnSpineAnimationStart;
   trackEntry.Interrupt += OnSpineAnimationInterrupt;
   trackEntry.End += OnSpineAnimationEnd;
   trackEntry.Dispose += OnSpineAnimationDispose;
   trackEntry.Complete += OnSpineAnimationComplete;
   trackEntry.Event += OnUserDefinedEvent;
}

public void OnSpineAnimationStart(TrackEntry trackEntry) {
   // Add your implementation code here to react to start events
}
public void OnSpineAnimationInterrupt(TrackEntry trackEntry) {
   // Add your implementation code here to react to interrupt events
}
public void OnSpineAnimationEnd(TrackEntry trackEntry) {
   // Add your implementation code here to react to end events
}
public void OnSpineAnimationDispose(TrackEntry trackEntry) {
   // Add your implementation code here to react to dispose events
}
public void OnSpineAnimationComplete(TrackEntry trackEntry) {
   // Add your implementation code here to react to complete events
}


string targetEventName = "targetEvent";
string targetEventNameInFolder = "eventFolderName/targetEvent";

public void OnUserDefinedEvent(Spine.TrackEntry trackEntry, Spine.Event e) {

   if (e.Data.Name == targetEventName) {
      // Add your implementation code here to react to user defined event
   }
}

// you can cache event data to save the string comparison
Spine.EventData targetEventData;
void Start () {
   targetEventData = skeletonAnimation.Skeleton.Data.FindEvent(targetEventName);
}
public void OnUserDefinedEvent(Spine.TrackEntry trackEntry, Spine.Event e) {

   if (e.Data == targetEventData) {
      // Add your implementation code here to react to user defined event
   }
}

详情请参考 Spine API文档.

在回调中更改AnimationState或游戏状态

虽然可以在 AnimationState 事件回调中通过调用 AnimationState.SetAnimation() 这类方法修改 AnimationState, 但同时需要考虑时点(timing)问题. 若通过事件回调更改游戏状态, 也会受到时点问题的影响.

  1. 使用 SkeletonAnimation.Update() 中应用动画后到 SkeletonAnimation.LateUpdate() 更新网格之前这段时间, 才会触发 AnimationState 事件回调.
  2. 如果在 AnimationState.End 事件回调中调用了AnimationState.SetAnimation(), 将会在同一帧中触发 AnimationState.Start 事件.
  3. 用mix从一个动画过渡到另一个动画时, 第二个动画的 Start 事件会在第一个动画的 End 事件 之前 触发. 修改游戏状态时常遇到由此引发的问题.

如果需要将事件回调中的触发推迟到下一个 Update() 循环, 使用 StartCoroutine 即可:

C#
trackEntry.End += e => {
   StartCoroutine(NextFrame(() => {
      YourCode();
   }));
};

...

IEnumerator NextFrame (System.Action call) {
   yield return 0;
   if (call != null)
      call();
}

Coroutine中的Yield指令

spine-unity运行时提供了几个用于Unity Coroutines的yield指令. 如果你尚不熟悉Unity Coroutines, 那么Coroutine教程Unity Coroutine文档 值得阅读一番.

以下是运行时提供的yield指令:

  1. WaitForSpineAnimation. 在 Spine.TrackEntry 触发某种特定事件前等待.

    C#
    var track = skeletonAnimation.state.SetAnimation(0, "interruptible", false);
    var completeOrEnd = WaitForSpineAnimation.AnimationEventTypes.Complete |
                          WaitForSpineAnimation.AnimationEventTypes.End;
    yield return new WaitForSpineAnimation(track, completeOrEnd);
  2. WaitForSpineAnimationComplete. 在 Spine.TrackEntry 触发 Complete 事件前等待.

    C#
    var track = skeletonAnimation.state.SetAnimation(0, "talk", false);
    yield return new WaitForSpineAnimationComplete(track);
  3. WaitForSpineAnimationEnd. 在 Spine.TrackEntry 触发 End 事件前等待.

    C#
    var track = skeletonAnimation.state.SetAnimation(0, "talk", false);
    yield return new WaitForSpineAnimationEnd(track);
  4. WaitForSpineEvent. 在 Spine.AnimationState 触发用户自定义的 Spine.Event 事件(事件名称在Spine编辑器中命名)前等待.

    C#
    yield return new WaitForSpineEvent(skeletonAnimation.state, "spawn bullet");
    // You can also pass a Spine.Event's Spine.EventData reference.
    Spine.EventData spawnBulletEvent; // cached in e.g. Start()
    ..
    yield return new WaitForSpineEvent(skeletonAnimation.state, spawnBulletEvent);

请注意: 和Unity自带的yield指令一样, 也可以复用spine-unity的yield实例以减少内存消耗.

教程页面

这里 可找到关于spine-unity事件的教程.

在编码中使用字符串属性特性

在检查器中手动输入动画名称十分繁琐易错. 为此spine-unity提供了一种将字符串参数转换为下拉框(popup)选项的方法. 在 string 属性前加上属性特性(property attribute), 运行时便能将该属性自动转换为一个下拉框中的可选字段, 例如你可以在下拉框中列出一个skeleton上的所有可用动画. 如果你在某个Spine组件中见过同样的字段, 那么在自定义组件中也可以通过属性特性实现相同字段的下拉框. 以下展示了可用的属性特性.

C#
[SpineBone] public string bone;
[SpineSlot] public string slot;
[SpineAttachment] public string attachment;
[SpineSkin] public string skin;
[SpineAnimation] public string animation;
[SpineEvent] public string event;
[SpineIkConstraint] public string ikConstraint;
[SpineTransformConstraint] public string transformConstraint;
[SpinePathConstraint] public string pathConstraint;

关于字符串属性特性的更多详细信息, 请见spine-unity包内置的 示例场景.

SkeletonMecanim组件

SkeletonMecanim 组件是在Unity中使用Spine skeleton的三种方式之一. 这三种方式分别是: 使用 SkeletonAnimation, SkeletonMecanim 或者 SkeletonGraphic (UI) 组件.

SkeletonMecanim 组件是 SkeletonAnimation 组件的Mecanim版本, 它使用Unity的Mecanim动画系统进行高级控制并结合Spine动画系统来负责skeleton的姿态和设置. 该组件使用Mecanim系统来确定应播放哪些Spine动画, 每个动画的轨道时间和透明度(alpha). 然后将对应的Spine动画 应用于Spine skeleton.

SkeletonAnimation 组件类似, 通过 SkeletonMecanim 也可以将Spine skeleton附加到GameObject上, 然后动画化该skeleton并处理各种动画事件等等. 在应用动画后到绘制skeleton前的这段时间里, 你可以用 SkeletonAnimation 一样的方式修改skeleton. 但相较于 SkeletonAnimation, 该组件有一些限制和额外需求:

请注意:SkeletonAnimation 组件不同的是, 如果要从前一段动画的时间轴状态平滑过渡(mix out)到下一段动画, SkeletonMecanim 强制要求后一段动画的时间轴首帧为关键帧. 更多信息参见下文中的 额外关键帧需求 一节.

请注意: TrackEntry.AttachmentThreshold 和类似的mix阈值功能在 SkeletonMecanim 中不可用.

SkeletonMecanim 组件提供的参数与 SkeletonAnimation组件 类似, 请查阅SkeletonAnimation 一节以了解 SkeletonMecanim 的参数信息.

额外关键帧需求

为了平滑地从上一段动画的时间轴状态(如骨骼旋转)过渡到下一段动画, 后者在setup pose时的首帧必须为一个关键帧. 否则无法清除前一段动画的时间轴状态. 这也是 SkeletonMecanim 相较于 SkeletonAnimation 组件的一个缺点.

简短示例: 前一段 jump 动画应平滑地过渡到 idle 动画. 假设 jump 动画结束时, 骨骼 bone1bone2 不在setup-pose位置上, 那么为了正确地mix out到 jump 状态必须将 idle 动画的首帧添加关键帧, 以照顾 bone1bone2 的位置(即把骨骼置为setup pose或任意自定义pose).

Auto Reset 参数可以重置动画状态, 但它在动画过渡结束时粗暴地快速mix out, 无法实现平滑过渡的效果.

此外, 在将skeleton导出为 .json.skel.bytes 格式时务必禁用 Animation cleanup 选项, 否则key进setup pose中的设置均不会被导出!

动画Blending的控制参数

SkeletonMecanim 在检查器中还暴露了以下参数:

  • Mecanim Translator

    • Auto Reset. 当其置为 true 后, 在一段动画结束时skeleton状态会mix out为setup pose. 如果一段动画改变了某些附件的可见性, 该功能就特别重要了: 当mix out时, 附件可见性将重置为setup pose状态, 否则当前附件状态将一直保持, 除非其他动画中的某个关键帧再次设置该附件.

    • Custom MixMode. 当禁用该参数后, 运行时会根据图层的blend模式使用 MixMode 的建议值. 而当启用该参数时则会在下方显示 Mix Modes 设置面板, 方便你为每个Mecanim图层单独指定 MixMode.

    • Mix Modes. 当启用了上文中的 Custom MixMode 选项后显示该面板. 在该面板中可以设置连续动画间的mix模式和跨图层动画间的mix模式.

      • Mix Next (建议用于 Base LayerOverride 图层)
        将动画应用于前一条轨道, 然后按照Mecanim过渡权重mix in到下一条轨道.
      • Always Mix (建议用于 Additive 图层)
        淡出前一条轨道中的动画(当启用了Auto Reset时则是淡出到setup pose), 并使用Mecanim过渡权重mix in到下一条轨道. 要注意在基础图层(base layer)上使用该选项可能会造成动画出现非预期的浸润(dipping)效果.
      • Hard (以前叫 Spine Style)
        将动画立即应用于下一条轨道.

Auto Reset 与图层 Mix Mode 参数的搭配结果

当动画有过渡时, 存在四种pose - current state last frame, setup pose, previous clip posenew clip pose - 其组合结果如下:

  1. current state last frame 开始 (或者在SkeletonMecanim更新前对该帧进行了修改).
  2. 应用 setup pose:
    • 当启用了 Auto Reset, setup pose 将取代 current state last frame.
    • 当禁用了 Auto Reset, setup pose 将不参与mix.

  3. 应用 previous clip pose:
    • 当mix模式置为 Always Mix 时, previous clip pose 将与当前状态mix (因此当启用 Auto Reset 后会mix setup pose).
    • 当mix模式置为 HardMix Next 时, 将按100%权重应用 previous clip pose 并完全覆盖当前状态 (因此它会覆盖 setup pose).

  4. 应用 new clip pose:
    • 当mix模式置为 Always MixMix Next 时, new clip pose 将与当前状态mix. 因此启用了 Auto ResetAlways Mix 将会是对 setup pose, previous clip posenew clip pose 三者的mix.
    • 当mix模式置为 Hard, 将按100%权重应用 new clip pose 并完全覆盖已应用的pose.

下表显示的是修改了相同时间轴值 (例如相同的骨骼旋转) 的当前剪辑片段(Clip) P 和新剪辑片段 N 的各种mix结果. S 代表启用了 Auto Reset 时的setup pose, 若禁用了 Auto Reset 则代表当前状态 (例如前一帧). w 表示过渡权重 (过渡开始时为0, 过渡结束时为1). 每种图层blend模式的默认mix模式(即推荐值)以粗体标出.

Always Mix Mix Next Hard
Base Layer lerp(lerp(S, P, 1-w), N, w) lerp(P, N, w) N
Override lerp(
lerp(lower_layers_result,
P, (1-w) * layer_weight),
N, w * layer_weight)
lerp(
lerp(lower_layers_result,
P, layer_weight),
N, w * layer_weight)
lerp(lower_layers_result,
N,
layer_weight)
Additive lower_layers_result +
layer_weight * lerp(P, N, w))
counts as Always Mix lower_layers_result +
layer_weight * N
缩写 含义
S Setup pose
P Previous clip pose
N New clip pose
w Transition weight
lerp(a, b, weight) Linear interpolation from a to b by weight.

SkeletonMecanim的控制器和动画器

SkeletonMecanim 组件和Unity Mecanim的 Animator 组件以相同的方式使用控制器(Controller)资产. 当通过 拖放 将一个skeleton实例化为 SkeletonMecanim 时, 运行时会自动生成并添加控制器资产.

请注意: 当启用 Apply Root Motion 后, 运行时会将 SkeletonMecanimRootMotion 组件自动添加到 SkeletonMecanim 的GameObject上.

将Spine动画拖放到Animator面板便能向控制器的动画状态机中添加动画. 添加过的动画将显示在控制器资产中.

SkeletonMecanim 将忽略在 [SkeletonDataAsset](/spine-unity-assets#Skeleton Data资产) 中设置的 Mix时长 值. 因为Mecanim是读取的是Animator面板中设置的过渡时间.

SkeletonMecanim事件

SkeletonMecanim 组件的事件存储在各个 AnimationClip 中, 其触发方式同其他Unity动画事件一样. 例如, 若在Spine中把事件命名为 "Footstep", 那么需要在 SkeletonMecanim 的GameObject上添加一个 MonoBehaviour 脚本, 使用名为 Footstep() 的方法来处理该事件. 当在Spine中使用文件夹时, 方法名则是文件夹名和动画名的拼接.例如, 若前文中的事件被放在一个名为 Foldername 的文件夹中时, 方法名将变为 FoldernameFootstep().

C#
public class YourComponentReceivingEvents : MonoBehaviour {
   // to capture event "Footstep" when it's placed outside of folders
   void Footstep() {
      Debug.Log("Footstep event received");
   }

   // to capture event "Footstep" when it's placed in a folder named "Foldername"
   void FoldernameFootstep () {
      Debug.Log("Footstep (in folder Foldername) received");
   }
}

关于Unity Mecanim事件的详细信息, 请参见 Unity的Animation Events文档.

SkeletonGraphic组件

SkeletonGraphic 组件是在Unity中使用Spine skeleton的三种方式之一. 这三种方式分别是: 使用 SkeletonAnimation, SkeletonMecanim 或者 SkeletonGraphic (UI) 组件.

SkeletonGraphic 组件是 SkeletonAnimation 组件的平替, 它使用Unity的UI系统进行布局、渲染和遮罩交互. 与 SkeletonAnimation 组件一样, 也能将Spine skeleton添加到GameObject中, 动画化Skeleton并响应动画事件.

我们为何特意设计一种UI组件?

Unity UI (UnityEngine.UI) 使用 CanvasCanvasRenderers 系统来排序和管理其可渲染对象. 内置的可渲染UI组件——例如 Text, Image, 和 RawImage ——都依赖于CanvasRenderer来工作. 将 MeshRenderers (如默认的Cube对象) 或 SpriteRenderers (如Sprite) 等对象置入UI, Unity也不会在 Canvas 中渲染它们. 由于SkeletonAnimation使用的是 MeshRenderers, 因此得到的对待同样是不予渲染. 因此spine-unity运行时提供了 SkeletonAnimation 的平替组件 SkeletonGraphic, 它是使用 CanvasRenderers 组件进行渲染的 UnityEngine.UI.MaskableGraphic 类的子类.

Material重要使用须知

SkeletonGraphic 组件中的material只能使用兼容 CanvasRenderer 的特殊着色器, 例如默认的 Spine/SkeletonGraphic* 着色器. 不要在 SkeletonGraphic 组件上使用URP、LWRP或如 Spine/Skeleton 这样的普通着色器. 没有肉眼可见的视效错误并不意味着这些着色器搭配上 SkeletonGraphic 可以正常工作. 现在已知这样的搭配会导致在移动设备上的无法正确渲染, 但在Unity编辑器中的渲染效果却没有任何问题. 和其他UI组件一样, SkeletonGraphic 使用 CanvasRenderer 而非 MeshRenderer 组件, 这两种组件使用了迥然不同的渲染管线.

SkeletonDataAsset 被实例化为 SkeletonGraphic 时, 组件将忽略 SpineAtlasAsset 中的普通material, 只使用其中的texture. 对此可以使用 SkeletonGraphicCustomMaterials 组件替换掉 SkeletonGraphic 组件中的materials.

重要须知: 由于Unity CanvasRenderer 组件本身的限制, SkeletonGraphic 默认情况下只能使用单张texture. 此时你可以在 SkeletonGraphic 的组件检查器中启用 Advanced - Multiple CanvasRenderers, 这样便能给每个子网格生成一个带 CanvasRenderer 的子 GameObject 来支持多张texture. 不过出于性能方面的考虑, 最好尽可能地避免这样做. 因此这意味着在UI中使用的Skeletons应被打包成一个single-texture (单页) atlas, 而非多页atlas. 要了解如何将附件图片打包为单页atlas texture, 请见 进阶操作 - 单页Texture Atlas导出与SkeletonGraphic 一节.

修正Shader配置和Materials

使用特殊着色器 (如"Spine/SkeletonGraphic Tint Black") 或在 CanvasGroup 中使用SkeletonGraphic时, 需为其生成顶点数据. 你可以在 Advanced - Vertex Data 检查器中找到相关参数, 包括 Tint Black, CanvasGroup CompatiblePMA Vertex Color. 运行时在这三种属性旁都放置了 Detect 按钮, 点击即可自动推断出正确设置. 运行时同时还提供了一个 Detect Settings 按钮可一键检测这三种属性设置是否配置有误.

修改了 Advanced - Vertex Data 设置后, 需更新material来适配新设置. 在 SkeletonGraphic 中, materials与textures相对独立, 因此material属性相同的不同skeleton可以使用同一个material. 基于此, 运行时针对常用material参数和着色器搭配提供了一种特殊的共享SkeletonGraphic material. 点击 Material 属性旁的 Detect 按钮, 运行时可根据 Advanced - Vertex Data 设置自动选择适合的material.

如果skeleton使用了多种blend模式并启用了 Advanced - Multiple CanvasRenderers 选项, 那么使用 Blend Mode Materials 旁的 Detect 按钮, 运行时也会为该blend模式自动选择material.

若发现最终显示效果与预期不符, 多半是atlas texture导入设置有误 (请参阅该节 进行错误排查).

CanvasGroup的alpha问题

当使用带有CanvasGroupSpine/SkeletonGraphic*着色器时, 你会发现随着 CanvasGroupAlpha 值的减小, 但skeleton却随之变亮, 效果如下图所示.

这是由于Unity在后台运算中修改了顶点色的alpha值, 而Unity的这一机制与spine-unity运行时的premultiplied alpha顶点着色器并不兼容.

重要须知: spine-unity 4.2及后续版本运行时新增了 Detect 按钮, 可自动设置 Advanced - Vertex Data 面板中的参数; 此外还添加了 Detect Material 按钮, 用于根据当前设置自动选择合适的material. 除非你打算了解全部细节, 否则可以跳过下两节内容.

不带Tint Black的SkeletonGraphic

本节内容适用于在material中使用了 Spine/SkeletonGraphic 着色器 (Spine/SkeletonGraphic TintBlack* 除外) 的情况. 如果在带有alpha淡出功能的 CanvasGroup 中使用此类material, 则Material必须启用 CanvasGroup Compatible 参数并禁用SkeletonGraphic组件的 PMA Vertex Colors 选项:

  1. a) 若使用spine-unity运行时4.2及后续版本, 你可以在 Materials/SkeletonGraphic-PMATexture/CanvasGroupCompatibleMaterials/SkeletonGraphic-StaightAlphaTexture/CanvasGroupCompatible 文件夹中找到两种 CanvasGroup Compatible 版的material.
    b) 而在运行时的既往版本中, 我们建议复制一份共享的 SkeletonGraphicDefault material并将其重命名为 SkeletonGraphic CanvasGroup 之类的名称, 而不要修改原版material. 这种共享的SkeletonGraphic material只需创建一份即可用在不同的skeleton上, 因为material与其所用的texture相互独立.
  2. 将这个 CanvasGroup Compatible 的material分配给 CanvasGroup 中的任意 SkeletonGraphic 组件, 换掉原版的material.
  3. 任何使用这种 CanvasGroup 兼容版material的 SkeletonGraphic 组件都需要一并禁用 Advanced - PMA Vertex Colors 选项, 以避免半透明部件的变暗效果超级加倍. 遗憾的是, 该选项将阻碍additive槽位和普通槽位的渲染合批, 进而导致绘制调用数上升.

带有Tint Black的SkeletonGraphic

在带有alpha淡出功能的 CanvasGroup 中使用附加了 Spine/SkeletonGraphic TintBlack* 着色器的material时, 需要进行以下设置:

  1. a) 正如前文所述, 若使用4.2及后续版本的spine-unity运行时, 请使用 CanvasGroupCompatible 文件夹中的 SkeletonGraphic TintBlack material
    b) 而在既往版本中, 则应复制一份共享的 SkeletonGraphicTintBlack material并将其重命名为 SkeletonGraphicTintBlack CanvasGroup 之类的名称, 而不要修改原版material. 这种共享的SkeletonGraphic material只需创建一份即可用在不同的skeleton上, 因为material与其所用的texture相互独立.
  2. SkeletonGraphic 组件中选择该material, 换掉原版的material.
  3. SkeletonGraphic 组件中启用 Advanced - CanvasGroup Compatible (在 4.1 或更早版本中则应启用 Advanced - Canvas Group Tint Black).
  4. a) 在spine-unity运行时4.2及后续版本中, 启用 SkeletonGraphicAdvanced - PMA Vertex Colors 选项与否均可. 然而运行时建议启用 Advanced - PMA Vertex Colors 选项, 因为在绘制调用中可以将additive槽位同普通槽位合批渲染.
    b) 在运行时的既往版本中, 请禁用 Advanced - PMA Vertex Colors 选项, 以避免半透明部件的变暗效果超级加倍.

边界与正确可见性

RectTransform的边界将决定 SkeletonGraphic 的可见性. 当拖放Skeleton将其实例化为 Canvas GameObject的子对象时, RectTransform边界会自动适配skeleton的initial pose. 单击 Match RectTransform with Mesh 按钮也能手动让RectTransform边界适配当前pose. 需要注意的重点是, RectTransform边界尺寸万万不能比网格小, 否则如 RectMask2D 这样的组件会认为RectTransform位于网格外, 即使需要被渲染的网格部件仍在RectTransform内部, 组件也会在绘制时忽略掉这个skeleton. 当激活了 RectTransform 工具 的五种Transform模式之一, 当前RectTransform边界将显示在场景视图中.

参数

SkeletonGraphic 组件的参数与 SkeletonAnimation 组件的参数类似, 请参考SkeletonAnimation 一节以了解更多参数信息.

SkeletonGraphic 检查器暴露的额外参数如下:

  • Material - Detect 按钮. 该按钮将根据 Advanced - Vertex Data 设置指定适合的 "Spine/SkeletonGraphic*" material. 与下文中 Advanced 面板里的 Detect Material 按钮参数相同.

  • Freeze. 当置为 true 时, 运行时将暂停更新 SkeletonGraphic.

  • Layout Scale Mode. SkeletonGraphic 支持基于 RectTransform 边界的自适应缩放. 默认值 None 表示保持已有行为. 将该参数设置为 Width Controls HeightHeight Controls Width, Fit In ParentEnvelope Parent (该项细节详见 该文档) 将启用自动缩放. 若需要修改参考布局边界, 请点击 Edit Layout Bounds 按钮进入编辑模式即可调整skeleton边界. 调整后运行时将自动缩放Skeleton, 以适应对象的 RectTransform 参考布局边界.

  • Edit Layout Bounds. 该选项用于修改上文中提到的 Layout Scale Mode 所用的参考布局边界, 点击该按钮将进入编辑模式, 然后就可以手动调整边界或者点击 Match RectTransform with Mesh 按钮自动调整边界以适配当前pose. 当完成调整后再次点击 Edit Layout Bounds 按钮即可退出编辑模式.

  • Match RectTransform with Mesh 点击 Match 按钮会使 SkeletonGraphic 的RectTransform匹配当前pose. 需要注意的重点是, RectTransform边界尺寸万万不能比网格小, 否则如 RectMask2D 这样的组件会认为RectTransform位于网格外, 即使需要被渲染的网格部件仍在RectTransform内部, 组件也会在绘制时忽略掉这个skeleton. 仅当 Layout Scale Mode 被置为 None, 或者点击了 Edit Layout Bounds 进入了编辑模式后, 该按钮才可用.

  • Advanced

    • Use Clipping. 当置为 false 时, 会忽略所有 Spine clipping(剪裁)附件.

    • Tint Black (!). 向网格中添加black tint顶点数据. 若有任意skeleton槽位设置了black tint, 则应启用该选项.
      Black tinting功能要求着色器将UV2和UV3解释为black tint色来实现着色效果, 比如运行时内置的 Spine/SkeletonGraphic Tint Black* 着色器就是这样工作的.
      要在父Canvas上使用UV2和UV3数据, 需要选中Canvas组件并启用 Additional Shader Channels 下的TexCoord1TexCoord2 选项.

      若SkeletonData中包含设置了tint black颜色的槽位, 则会显示一个 Detect 按钮来启用该参数, 反之则将禁用该参数.

    • CanvasGroup Compatible. 当在 CanvasGroup 中使用SkeletonGraphic时应启用该选项. 也需要在Material中启用 CanvasGroup Compatible, 因此在此设置更改后请通过 Detect Material 按钮或手动分配来重新选择适合的material.
      当使用 SkeletonGraphic Tint Black* 着色器时同时启用了 Tint BlackPMA Vertex Color 选项, 此时运行时将在 uv2.g 中存储alpha值而非将其存储于主顶点色 color.a, 同时 color.a 将被置为常量 1.0 以支持 CanvasGroupcolor.a 的更改.

      若SkeletonGraphic组件位于 CanvasGroup 层次结构中, 则会显示一个 Detect 按钮来启用该参数, 反之则将禁用该参数.

    • [4.1或更早版本中的] Canvas Group Tint Black. 仅在使用 SkeletonGraphic Tint Black* 着色器时启用该选项. 在禁用 Tint BlackPMA Vertex Color 后该选项不生效. 在 CanvasGroup 中的 SkeletonGraphic 组件, 若其槽位中使用了 Additive blend模式时应启用该参数. 启用该参数后, 运行时将在 uv2.g 中存储alpha值而非将其存储于主顶点色 color.a, 同时 color.a 将被置为常量 1.0 以支持 CanvasGroupcolor.a 的更改.

    • PMA Vertex Colors (用于解决 CanvasGroup alpha 问题). 该选项将把顶点色的RGB与其alpha值相乘. 默认启用此参数; 若不是正在使用第三方着色器, 或无需在非 "SkeletonGraphic TintBlack*" 着色器上启用 CanvasGroup Compatible 选项, 则默认值即为准确设置.

      正确设置了上述的 Tint BlackCanvasGroup Compatible 选项后界面中会显示 Detect 按钮. 如果使用了第三方 (即非Spine) 着色器, 或启用了 CanvasGroup Compatible 的同时禁用了 Tint Black 选项, Detect 按钮将禁用该参数. 反之它则会启用该参数.

      应用 PMA Vertex Color 的具体规则 (仅当 Detect 失效时才需关注这些规则) 如下:

      使用 spine-unity 4.2 或后续版本时:
      应启用该参数的场合为 (默认启用):

      • 使用了 "Spine/SkeletonGraphic*" 着色器却禁用了 CanvasGroup Compatible 选项 (无关 Straight Alpha Texture 的启用与否),
      • 使用了 "Spine/SkeletonGraphic TintBlack*" 着色器 (无关 Straight Alpha Texture 的启用与否),
      • 使用了需要顶点色作PMA的第三方着色器 (例如使用了PMA additive输出blend模式 Blend One OneMinusSrcAlpha 的着色器).

      应禁用该参数的场合为:

      • 未使用 "Spine/SkeletonGraphic TintBlack*" 着色器, 而是启用了 CanvasGroup Compatible 的普通 "Spine/SkeletonGraphic*" 着色器,
      • 使用了需要常规顶点色的第三方着色器 (例如使用了普通输出blend模式 Blend SrcAlpha OneMinusSrcAlpha 的着色器).

      使用 spine-unity 4.1 或更早的版本时:
      应启用该参数的场合为 (默认启用):

      • 使用了 "Spine/SkeletonGraphic*" 着色器却禁用了 CanvasGroup Compatible 选项 (无关 Straight Alpha Texture 的启用与否),
      • 使用了需要顶点色作PMA的第三方着色器 (例如使用了PMA additive输出blend模式 Blend One OneMinusSrcAlpha 的着色器).

      应禁用该参数的场合为:

      • 使用启用了 CanvasGroup Compatible"Spine/SkeletonGraphic*" 着色器,
      • 使用了需要常规顶点色的第三方着色器(例如使用了普通输出blend模式 Blend SrcAlpha OneMinusSrcAlpha 的着色器).

      启用 PMA Vertex Color 参数后, 在一次绘制调用中将把additive槽位和普通槽位合批渲染. 而禁用该选项后, 则需要在additive槽位上启用 SkeletonDataBlend Mode Materials - Apply Additive Material 选项, 但这样做会产生额外绘制调用, 从而降低性能表现.

    • Detect Settings. 该按钮可一键检测 Tint Black, CanvasGroup CompatiblePMA Vertex Colors 参数.

    • Detect Material. 根据前文中的 Tint BlackCanvasGroup Compatible 参数及atlas texture的导入设置(即PMA和straight alpha 设置), 运行时将自动选择适合的 SkeletonGraphic material. 如果显示结果不正确, 很可能是你的texture设置有误(请参阅该节 进行错误排查).

    • Multiple CanvasRenderers. 当该选项置为 true 时, SkeletonGraphic 将不再局限于单个 CanvasRenderer, 而是对应每个绘制调用 (即子网格) 自动创建子 CanvasRenderer GameObjects. 如此做法虽可以突破组件的 单页txture限制, 但会增大性能开销.

      • Blend Mode Materials. 在这里可以为各种槽位Blend模式指定不同的 SkeletonGraphic material. 不过此处仅支持兼容 "Spine/SkeletonGraphic *" 或其他兼容 CanvasRenderer 的Materials.

      • 点击 Assign Default 运行时将选择默认blend模式的material SkeletonGraphicAdditive, SkeletonGraphicMultiplySkeletonGraphicScreen.

        请注意: 应确保正确设置了 SkeletonDataAssetBlend Mode Materials, 因为 SkeletonGraphic 的blend模式material依赖于 SkeletonDataAsset 的material. 当启用 PMA Vertex Colors 后, 运行时将忽略 Additive Material 设置.

    • Animation Update. 该选项设置是以普通的 Update(默认) 更新动画, 或是基于物理的 FixedUpdate 模式更新动画, 还是通过用户调用来手动更新动画. 当使用 SkeletonRootMotion 组件并分配了 Rigidbody 或者 Rigidbody2D 后, 我们推荐使用 In FixedUpdate 更新模式, 其他场合则建议设置为 In Update.

    • Update When Invisible. 设置当MeshRenderer不可见时应该使用何种更新模式. 当网格再次可见时更新模式会自动重置为 UpdateMode.FullUpdate.

    • Separator Slot Names.Enable Separationtrue 时, 该选项表示渲染分割点的槽位名称. 关于渲染分割(render separation)的详情, 请参考 SkeletonRenderSeparator 一节, 不过使用 SkeletonGraphic 时无需额外组件便能实现渲染分割.

    • Enable Separation. 该选项无需添加任何额外组件 (如 SkeletonRenderSeparatorSkeletonRendererSkeletonPartsRenderer 组件) 便能直接启用渲染分割. 启用该选项后运行时将自动创建额外部件的GameObjects, 同时附加所需的 CanvasRenderer GameObjects到层次结构中. 可在层次结构中按需调整部件GameObjects的位置, 以便调整组件在 Canvas 中的绘制顺序.

    • Update Part Location. 当启用该选项后, 将更新部件GameObject的位置以匹配 SkeletonGraphic 的位置. 如果需要将部件重新挂载到其他GameObject下时, 该功能将会派上用场.

    • Physics Inheritance. 该项控制Transform的运动将如何应用于skeleton的PhysicsConstraints.

      • Position. 该选项中的X和Y缩放值将用于加权Transform的位移量. 当该值非零时, 将把加权Transform位移量应用于skeleton的物理约束. 几种 (X,Y) 的典型值是:
        (1,1) 表示正常应用 XY 移动,
        (2,2) 说明应用了双倍速移动,
        (1,0) 表示仅应用水平移动, 而
        (0,0) 表示完全不影响Transform位移.
      • Rotation. 当该值非零时, 将把该值用于加权Transform的旋转量, 加权旋转量将应用于skeleton的物理约束. 其几种典型值为:
        1 表示正常旋转,
        2 表示二倍速旋转, 而
        0 代表完全不影响Transform旋转.
      • Movement relative to. 设置Transform移动的参考Transform(如父级Transform等), 该选项表示物理约束计算相对移动时应参考的Transform. 将此项置为 None 即表示使用绝对世界位置 (默认值).

示例场景

示例场景 Spine Examples/Getting Started/6 Skeleton Graphic 中演示了 SkeletonGraphic 组件的基本用法. 在 Spine Examples/Other Examples/SkeletonRenderSeparator 中提供了高级示例场景, 它展示了如何设置渲染分割槽位并修改绘制顺序.

SkeletonRenderer组件

SkeletonRenderer 组件负责绘制skeleton的当前状态. 它是 SkeletonAnimationSkeletonMecanim 组件的基类.

请注意: 大多数时候都应使用 SkeletonAnimationSkeletonMecanimSkeletonGraphic (UI) 组件. 这些组件提供了丰富的动画控制方法. 只有在手动应用无过渡动画 (比如UI仪表元素) 时才可能用得上SkeletonRenderer组件.

SkeletonRenderer组件通过更新 MeshRenderer 中的程序化网格进行渲染. 该组件使用 SkeletonDataAsset 中引用的texture atlas资产来查找绘制skeleton附件所需的textures和materials. 请查阅 SkeletonAnimation 一节了解更多信息.

示例场景 Spine Examples/Other Examples/SpineGauge 演示了如何直接使用 SkeletonRenderer 组件.

下一节: 工具组件 上一节: 资产