Spine Skeleton 组件支持 Spine 官方工具导出的数据格式,并对 Spine(骨骼动画)资源进行渲染和播放。
在 层级管理器 中选中需要添加 ArmatureDisplay 组件的节点,然后点击 属性检查器 下方的 添加组件 -> Spine -> Skeleton 按钮,即可添加 Skeleton 组件到节点上。
-
Spine Skeleton 相关的脚本接口请参考 Skeleton API。
属性 | 功能说明 |
---|---|
CustomMaterial | 自定义材质,可用于实现溶解、外发光等渲染效果。详情请参考 自定义材质。 |
Color | 设置骨骼动画颜色 |
SkeletonData | 骨骼信息数据,拖拽 Spine 导出后的骨骼资源到该属性中 |
Default Skin | 选择默认的皮肤 |
Animation | 当前播放的动画名称 |
Animation Cache Mode | 渲染模式,包括 REALTIME(默认)、SHARED_CACHE 和 PRIVATE_CACHE 三种。 1. REALTIME 模式,实时运算,支持 Spine 所有的功能。 2. SHARED_CACHE 模式,将骨骼动画及贴图数据进行缓存并共享,相当于预烘焙骨骼动画。拥有较高性能,但不支持动作融合和动作叠加,只支持动作开始和结束事件。至于内存方面,当创建 N(N>=3)个相同骨骼、相同动作的动画时,会呈现内存优势。N 值越大,优势越明显。综上 SHARED_CACHE 模式适用于场景动画、特效、副本怪物、NPC 等,能极大提高帧率和降低内存。 3. PRIVATE_CACHE 模式,与 SHARED_CACHE 类似,但不共享动画及贴图数据,且会占用额外的内存,仅存在性能优势,如果大量使用该模式播放动画可能会造成卡顿。若想利用缓存模式的高性能,但又存在换装需求(不能共享贴图数据)时,那么 PRIVATE_CACHE 就适合你。 |
Loop | 是否循环播放当前动画 |
PremultipliedAlpha | 图片是否启用贴图预乘,默认为 True。 当图片的透明区域出现色块时需要关闭该项,当图片的半透明区域颜色变黑时需要启用该项。 |
TimeScale | 当前骨骼中所有动画的时间缩放率 |
DebugSlots | 是否显示 Slot 的 Debug 信息 |
DebugBones | 是否显示骨骼的 Debug 信息 |
DebugMesh | 是否显示 Mesh 的 Debug 信息 |
UseTint | 是否开启染色效果,默认关闭。 |
Sockets | 用于将某些外部节点挂到指定的骨骼关节上,属性的值表示挂点的数量。详情请参考下文介绍。 |
注意:
- 当使用 Spine Skeleton 组件时,属性检查器 中 Node 组件上的 Anchor 与 Size 属性是无效的。
- Spine Skeleton 组件属于 UI 渲染组件,而 Canvas 节点是 UI 渲染的 渲染根节点,所以带有该组件的节点必须是 Canvas 节点(或者是带有 RenderRoot2D 组件的节点)的子节点才能在场景中正常显示。
- 当使用 Spine Skeleton 组件时,由于拥有 UseTint 属性,所以其自定义材质需要有两个颜色信息,可参考引擎内置的 builtin-spine.effect(GitHub | Gitee)实现,否则 Spine 的染色效果可能会出错。
下面通过一个范例介绍 Spine 如何换装。
-
首先在 层级管理器 中新建一个 Canvas 节点,然后在 Canvas 节点下新建一个空节点并命名为 girl。选中 girl 节点并在 属性检查器 中添加 Skeleton 组件,将资源拖拽至 Skeleton 组件的 SkeletonData 属性框中。可更改 Skeleton 组件的 Animation 属性用于设置想要播放的动画。
-
在 资源管理器 中新建一个 TypeScript 脚本并命名为 SpineSkin,编写组件脚本。脚本代码如下:
import { _decorator, Component, sp } from 'cc'; const { ccclass, property } = _decorator; @ccclass('SpineSkin') export class SpineSkin extends Component { @property({ type:sp.Skeleton }) spine: sp.Skeleton | null = null; skinId: number = 0; start () { // Your initialization goes here. } change() { const skins =['girl', 'boy', 'girl-blue-cape', 'girl-spring-dress'].map(x=> `full-skins/${x}`); this.skinId = (this.skinId + 1) % skins.length; this.spine!.setSkin(skins[this.skinId]); } // update (deltaTime: number) { // // Your update function goes here. // } }
-
然后将
SpineSkin
脚本挂载到 Canvas 节点上,即将脚本拖拽到 Canvas 节点的 属性检查器 中。再将 层级管理器 中的girl
节点拖拽到SpineSkin
脚本组件对应的属性框中,并保存场景。 -
接下来我们需要利用 Button 组件的点击事件来触发 SpineSkin 脚本中的
change
回调,实现通过点击按钮来切换皮肤。在 层级管理器 的 Canvas 节点下新建一个 Button 节点并命名为
change_skin
,根据需要调整其位置、大小、文字显示等属性。然后在 属性检查器 中设置
change_skin
节点的点击事件,将挂载了SpineSkin
脚本组件的 Canvas 节点拖拽到ClickEvents
属性的cc.Node
属性框中,指定脚本组件为SpineSkin
,并设置回调为change
: -
根据需要调整场景结构,保存场景后点击编辑器上方的预览按钮,点击 change skin 按钮,可以看到人物皮肤已被替换。
注意:若预览时未显示场景,请检查各节点的 Layer 属性是否与 Camera 节点的保持一致。
顶点效果只有当 Spine Skeleton 组件的 Animation Cache Mode 属性设置为 REALTIME 模式时有效,下面通过一个范例介绍 Spine 如何设置顶点效果。
-
首先在 层级管理器 中新建一个 Canvas 节点,然后在 Canvas 节点下新建一个空节点并命名为
Spine
。选中Spine
节点,并在 属性检查器 中添加 Skeleton 组件,将资源拖拽到 Skeleton 组件的 SkeletonData 属性框中,设置好 Skeleton 组件属性。 -
在 资源管理器 中新建一个 TypeScript 脚本并命名为
SpineExample
,编写组件脚本。脚本代码如下:import { _decorator, Component, sp } from 'cc'; const { ccclass, property } = _decorator; @ccclass('SpineExample') export class SpineExample extends Component { @property({ type:sp.Skeleton }) skeleton: sp.Skeleton | null = null; private _jitterEffect?:sp.VertexEffectDelegate; start () { this._jitterEffect = new sp.VertexEffectDelegate(); // 设置好抖动参数。 this._jitterEffect.initJitter(20, 20); // 调用 Skeleton 组件的 setVertexEffectDelegate 方法设置效果。 this.skeleton!.setVertexEffectDelegate(this._jitterEffect!); } };
-
然后将
SpineExample
脚本挂载到 Canvas 节点上,即将脚本拖拽到节点的 属性检查器 中。再将 层级管理器 中挂载了 Skeleton 组件的Spine
节点拖拽到脚本组件对应的 Skeleton 属性框中,并保存场景。 -
点击编辑器上方的预览按钮,即可看到 Spine 动画顶点抖动的效果。详情可参考 SpineMesh(GitHub | Gitee)范例。
在使用骨骼动画时,经常需要在骨骼动画的某个部位上挂载节点,以实现节点与骨骼动画联动的效果。我们可以通过使用编辑器和脚本两种方式来实现 Spine 挂点。下面通过一个范例来介绍 Spine 如何使用挂点将星星挂在龙的尾巴上,并随一起晃动。
-
首先在 层级管理器 中新建一个 Canvas 节点,然后在 Canvas 节点下新建一个空节点并命名为
Spine
。选中Spine
节点,并在 属性检查器 中添加 Skeleton 组件,将资源拖拽到 Skeleton 组件的 SkeletonData 属性框中,设置好 Skeleton 组件属性。 -
在 层级管理器 中右键点击 Spine 节点,选择 创建 -> 2D 对象 -> Sprite 为其添加一个子节点并命名为
star
。然后将星星资源拖拽到 属性检查器 中 Sprite 组件的SpriteFrame
属性上。 -
在 层级管理器 中选中 Spine 节点,在 属性检查器 中将 Skeleton 组件的 Sockets 属性设置为 1(Sockets 属性的值代表了挂点的数量)。
-
然后设置 Sockets 中的
Path
和Target
属性,Path
的下拉框中会列出所有的骨骼,选择想要挂载的目标骨骼,这里以龙的尾巴为例,然后将star
节点拖拽到Target
属性框中。即可在 场景编辑器 中看到星星挂在了龙的尾巴上。 -
保存场景,点击编辑器上方的预览按钮,也可以看到星星挂在龙的尾巴上,并随着龙的尾巴一起晃动。具体可参考官方 SpineAttach(GitHub | Gitee)范例。
-
前两个步骤与通过编辑器实现的一致。
-
在 资源管理器 中新建一个 TypeScript 脚本并命名为
SpineAttach
,编写组件脚本。脚本代码如下:import { _decorator, Component, sp, Label, Node } from 'cc'; const { ccclass, property } = _decorator; @ccclass('SpineAttach') export class SpineAttach extends Component { @property({ type: sp.Skeleton }) skeleton: sp.Skeleton = null!; @property({ type: Node }) attachNode: Node = null!; start () { var socket = new sp.SpineSocket("root/hip/tail1/tail2/tail3/tail4/tail5/tail6/tail7/tail8/tail9/tail10", this.attachNode); // 第一个参数传入的是挂点的目标骨骼。第二个参数传入的是挂点的节点 this.skeleton!.sockets.push(socket); this.skeleton!.sockets = this.skeleton!.sockets; } }
注意:若不知道目标骨骼的名称,可将 Spine 组件中的 Sockets 属性设置为 1,然后在
Path
的下拉框中查找所需的目标骨骼名称。查找完成后再将 Sockets 属性还原为 0 即可。 -
然后将
SpineAttach
脚本挂载到 Canvas 节点或者其他节点上,即将脚本拖拽到节点的 属性检查器 中。再将 层级管理器 中挂载了 Skeleton 组件的Spine
节点和star
节点分别拖拽到脚本组件对应的 Skeleton 属性框和 AttachNode 属性框中,并保存场景。 -
点击编辑器上方的预览按钮,即可看到星星挂在龙的尾巴上,并随着龙的尾巴一起晃动。
通过 Spine 挂点功能可以对骨骼动画的某个部位做碰撞检测。下面通过一个范例来介绍 Spine 如何实现碰撞检测,通过判断人物脚与地面接触与否来实现当人物跑动时,动态地改变地面颜色。
-
首先需要在编辑器菜单栏的 项目 -> 项目设置 -> 功能裁剪 中将 2D 物理系统 设置为 内置 2D 物理系统。
-
与 Spine 挂点的前两个步骤一样,创建好 Spine 节点和其空子节点(命名为
frontFoot
),以及 Sprite 节点作为地面(命名为Ground
),并设置好位置大小等属性。 -
在 层级管理器 中选中 frontFoot 节点,在 属性检查器 中点击 添加组件 -> Physics2D -> Colliders -> Polygon Collider2D 添加碰撞组件,然后设置好碰撞组件参数。
参考通过编辑器实现 Spine 挂点的第 3、4 个步骤,将 frontFoot 节点挂载到 Sprite 节点的目标骨骼上(例如脚上),frontFoot 节点便会随着骨骼动画一起运动,从而碰撞组件的包围盒也会实时地与骨骼动画保持同步。
-
在 层级管理器 中选中 Ground 节点,在 属性检查器 中点击 添加组件 -> Physics2D -> Colliders -> BoxCollider2D 添加碰撞组件,然后设置好碰撞组件参数。
-
在 资源管理器 中新建一个 TypeScript 脚本并命名为
SpineCollider
,然后将脚本挂载到地面节点Ground
上。脚本代码如下:import { _decorator, Component, Node, PhysicsSystem2D, Contact2DType, Collider2D, Color, Sprite, ParticleSystem2D, EPhysics2DDrawFlags } from 'cc'; const { ccclass } = _decorator; @ccclass('SpineCollider') export class SpineCollider extends Component { touchingCountMap : Map < Node, number > = new Map; private debugDrawFlags : number = 0; start () { // Your initialization goes here. PhysicsSystem2D.instance.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); PhysicsSystem2D.instance.on(Contact2DType.END_CONTACT, this.onEndContact, this); this.debugDrawFlags = PhysicsSystem2D.instance.debugDrawFlags; } onEnable () { PhysicsSystem2D.instance.debugDrawFlags = this.debugDrawFlags | EPhysics2DDrawFlags.Shape; } onDisable () { PhysicsSystem2D.instance.debugDrawFlags = this.debugDrawFlags; } addContact (c: Collider2D) { let count = this.touchingCountMap.get(c.node) || 0; this.touchingCountMap.set(c.node, ++count); let sprite = c.getComponent(Sprite); if (sprite) { sprite.color = Color.RED; } } removeContact (c: Collider2D) { let count = this.touchingCountMap.get(c.node) || 0; --count; if (count <= 0) { this.touchingCountMap.delete(c.node); let sprite = c.getComponent(Sprite); if (sprite) { sprite.color = Color.WHITE; } } else { this.touchingCountMap.set(c.node, count); } } onBeginContact (a: Collider2D, b: Collider2D) { this.addContact(a); this.addContact(b); } onEndContact (a: Collider2D, b: Collider2D) { this.removeContact(a); this.removeContact(b); } }
-
点击编辑器上方的预览按钮,即可看到效果。具体可参考 SpineCollider(GitHub | Gitee)范例。
注意:由于挂点的实现机制,会导致基于挂点的碰撞检测存在延迟一帧的问题。